<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/wang-cheng/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/wang-cheng/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/wang-cheng/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/wang-cheng/images/logo.svg" color="#222">

<link rel="stylesheet" href="/wang-cheng/css/main.css">


<link rel="stylesheet" href="/wang-cheng/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"cheng-wang123.gitee.io","root":"/wang-cheng/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
<meta property="og:type" content="website">
<meta property="og:title" content="诚の博客">
<meta property="og:url" content="https://cheng-wang123.gitee.io/wang-cheng/index.html">
<meta property="og:site_name" content="诚の博客">
<meta property="og:description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="王诚">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://cheng-wang123.gitee.io/wang-cheng/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>诚の博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/wang-cheng/atom.xml" title="诚の博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/wang-cheng/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">诚の博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录学习中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/wang-cheng/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/wang-cheng/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/wang-cheng/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/wang-cheng/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/23/%E9%9D%A2%E7%BB%8F%E4%B8%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/05/23/%E9%9D%A2%E7%BB%8F%E4%B8%89/" class="post-title-link" itemprop="url">面经三</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2022-05-23 13:00:40 / 修改时间：15:32:07" itemprop="dateCreated datePublished" datetime="2022-05-23T13:00:40+08:00">2022-05-23</time>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="Java项目打包成jar包和war包的区别，什么情况打成jar，什么情况打成war"><a href="#Java项目打包成jar包和war包的区别，什么情况打成jar，什么情况打成war" class="headerlink" title="Java项目打包成jar包和war包的区别，什么情况打成jar，什么情况打成war"></a><strong>Java项目打包成jar包和war包的区别，什么情况打成jar，什么情况打成war</strong></h2><p>jar包叫做Java 归档文件，是与平台无关的文件格式。Java项目中打包成的jar包，里面放的是class文件，配置文件，可是没有静态资源的文件</p>
<p>war包表示这是一个Java的Web应用程序的包，是一个可以直接运行的web模块，通常用于网站，打成包部署到容器中。</p>
<p>jar包：直接通过内置Tomcat运行，不需要额外安装Tomcat。如需修改内置Tomcat的配置，只需要在SpringBoot的配置文件中配置。内置Tomcat没有自己的日志输出，全靠jar包应用输出日志。但是比较方便，快速，比较简单。</p>
<p>war包：传统的应用交付方式，需要安装Tomcat，然后放到wabapps目录下运行war包，可以灵活选择Tomcat版本，可以直接修改Tomcat的配置，有自己的Tomcat日志输出，可以灵活配置安全策略,相对打成jar包来说没那么快速方便。</p>
<h2 id="mybatis-中如何传递多个参数"><a href="#mybatis-中如何传递多个参数" class="headerlink" title="mybatis 中如何传递多个参数"></a><strong>mybatis 中如何传递多个参数</strong></h2><ol>
<li><p>顺序传参法</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="keyword">select</span> id=<span class="string">&quot;selectUser&quot;</span> resultMap=<span class="string">&quot;UserResultMap&quot;</span>&gt;</span><br><span class="line">    <span class="keyword">select</span> * <span class="keyword">from</span> user</span><br><span class="line">    <span class="keyword">where</span> user_name = <span class="meta">#&#123;0&#125; and dept_id = #&#123;1&#125;</span></span><br><span class="line">&lt;/<span class="keyword">select</span>&gt;</span><br></pre></td></tr></table></figure>
</li>
<li><p>@Param注解传参法</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="keyword">select</span> id=<span class="string">&quot;selectUser&quot;</span> resultMap=<span class="string">&quot;UserResultMap&quot;</span>&gt;</span><br><span class="line">    <span class="keyword">select</span> * <span class="keyword">from</span> user</span><br><span class="line">    <span class="keyword">where</span> user_name = <span class="meta">#&#123;userName&#125; and dept_id = #&#123;deptId&#125;</span></span><br><span class="line">&lt;/<span class="keyword">select</span>&gt;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Map传参法</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="keyword">select</span> id=<span class="string">&quot;selectUser&quot;</span> parameterType=<span class="string">&quot;java.util.Map&quot;</span> resultMap=<span class="string">&quot;UserResultMap&quot;</span>&gt;</span><br><span class="line">    <span class="keyword">select</span> * <span class="keyword">from</span> user</span><br><span class="line">    <span class="keyword">where</span> user_name = <span class="meta">#&#123;userName&#125; and dept_id = #&#123;deptId&#125;</span></span><br><span class="line">&lt;/<span class="keyword">select</span>&gt;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Java Bean传参法</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="keyword">select</span> id=<span class="string">&quot;selectUser&quot;</span> parameterType=<span class="string">&quot;com.test.User&quot;</span> resultMap=<span class="string">&quot;UserResultMap&quot;</span>&gt;</span><br><span class="line">    <span class="keyword">select</span> * <span class="keyword">from</span> user</span><br><span class="line">    <span class="keyword">where</span> user_name = <span class="meta">#&#123;userName&#125; and dept_id = #&#123;deptId&#125;</span></span><br><span class="line">&lt;/<span class="keyword">select</span>&gt;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="三大集合的区别"><a href="#三大集合的区别" class="headerlink" title="三大集合的区别"></a><strong>三大集合的区别</strong></h2><ul>
<li>Set集合：存放对象的引用，不允许有重复对象。<ul>
<li>HashSet</li>
<li>TreeSet</li>
</ul>
</li>
<li>List集合：主要实现类有LinkedList、ArrayList，前者实现了链表结构，后者可代表大小可变的数组。<ul>
<li>LinkedList</li>
<li>ArrayList</li>
</ul>
</li>
<li>Map集合：Map对值没有唯一性要求，对健要求唯一，如果加入已有的健，原有的值对象将被覆盖。<ul>
<li>HashMap</li>
<li>TreeMap</li>
</ul>
</li>
</ul>
<ol>
<li>List 集合中对象按照索引位置排序，可以有重复对象，允许按照对象在集合中的索引位置检索对象，例如通过list.get(i)方法来获取集合中的元素；</li>
<li>Map 中的每一个元素包含一个键和一个值，成对出现，键对象不可以重复，值对象可以重复；</li>
<li>Set 集合中的对象不按照特定的方式排序，并且没有重复对象，但它的实现类能对集合中的对象按照特定的方式排序，例如 Tree Set 类，可以按照默认顺序，也可以通过实现 Java.util.Comparator&lt; Type &gt;接口来自定义排序方式。 </li>
</ol>
<h2 id="Java中的io方式"><a href="#Java中的io方式" class="headerlink" title="Java中的io方式"></a><strong>Java中的io方式</strong></h2><p>BIO（同步阻塞io）：一个线程负责连接 、一请求一应答、缺乏弹性伸缩能力（线程数增加后系统性能极具下降）；</p>
<p>NIO（同步非阻塞io）：缓冲区Buffer（NIO中的数据读写都是通过缓冲区buffer操作）、通道Channel（可以是双向的，流是单向的）、多路复用器Selector（轮询出就绪状态的Channel的集合，可以进行后续的IO操作，没有最大连接数限制）；</p>
<h2 id="io输入与输出的分别相对于谁来说"><a href="#io输入与输出的分别相对于谁来说" class="headerlink" title="io输入与输出的分别相对于谁来说"></a><strong>io输入与输出的分别相对于谁来说</strong></h2><p>IO口，I就是input，O是output，芯片io口输入是外面信号传输到芯片、输出是芯片内部传输信号到其他器件。</p>
<p>输入输出是相对的，比如信号从A——&gt;B，对于A来说是输出，对于B来说是输入</p>
<h2 id="spring-mvc的基本结构和程序流程"><a href="#spring-mvc的基本结构和程序流程" class="headerlink" title="spring mvc的基本结构和程序流程"></a><strong>spring mvc的基本结构和程序流程</strong></h2><ol>
<li><p>DispatcherServlet：中央控制器，把请求给转发到具体的控制类</p>
</li>
<li><p>Controller：具体处理请求的控制器</p>
</li>
<li><p>handlerMapping：映射处理器，负责映射中央处理器转发给controller时的映射策略</p>
</li>
<li><p>ModelAndView：服务层返回的数据和视图层的封装类</p>
</li>
<li><p>ViewResolver：视图解析器，解析具体的视图</p>
</li>
<li><p>Interceptors ：拦截器，负责拦截我们定义的请求然后做处理工作</p>
</li>
</ol>
<h2 id="spring事务的了解"><a href="#spring事务的了解" class="headerlink" title="spring事务的了解"></a><strong>spring事务的了解</strong></h2><p>Spring的事务分为两种，一种是编程式TranscationTemplate，另一种是声明式。声明式是借助AOP实现的。在目标方法启动之前调用开启一个事务，根据方法的执行结果进行提交或者回滚。</p>
<p>事务的7种传播机制</p>
<h2 id="Springboot中bean的生命周期"><a href="#Springboot中bean的生命周期" class="headerlink" title="Springboot中bean的生命周期"></a><strong>Springboot中bean的生命周期</strong></h2><ul>
<li><p>Bean的定义</p>
<ol>
<li><p>第一步，资源定位，就是Spring根据我们定义的注解(@Component)，找到相应的类。</p>
</li>
<li><p>找到了资源就开始解析，并将定义的信息保存起来，此时，并没有初始化bean,这点需要注意。</p>
</li>
<li><p>然后将bean的定义发布到SpringIoc的容器中，此时,SpringIoc的容器中还是没有Bean的生成。只是定义的信息。</p>
</li>
</ol>
</li>
<li><p>Bean的初始化</p>
<ol>
<li>bean实例化(此处有三级缓存)</li>
<li>填充属性</li>
<li>执行aware接口需要实现的方法</li>
<li>执行BeanPostProcessor:before</li>
<li>bean初始化</li>
<li>执行BeanPostProcessor:after(AOP)</li>
</ol>
</li>
<li><p>Bean的生存期</p>
</li>
<li><p>Bean的销毁</p>
</li>
</ul>
<h2 id="对Redis了解多吗？你觉得Redis最核心的功能是什么？"><a href="#对Redis了解多吗？你觉得Redis最核心的功能是什么？" class="headerlink" title="对Redis了解多吗？你觉得Redis最核心的功能是什么？"></a><strong>对Redis了解多吗？你觉得Redis最核心的功能是什么？</strong></h2><p>缓存</p>
<h2 id="假设HashMap里面存放100万个对象，那么gc可能会有什么问题？"><a href="#假设HashMap里面存放100万个对象，那么gc可能会有什么问题？" class="headerlink" title="假设HashMap里面存放100万个对象，那么gc可能会有什么问题？"></a><strong>假设HashMap里面存放100万个对象，那么gc可能会有什么问题？</strong></h2><p>对象太多，使用可达性分析法去扫描这些无用对象的时候花费的时间比较长，gc花费的时间就会变长。</p>
<h2 id="进程调度算法"><a href="#进程调度算法" class="headerlink" title="进程调度算法"></a><strong>进程调度算法</strong></h2><ol>
<li><p>先来先服务调度算法（FCFS）：先来先去服务调度算法是一种最简单的调度算法，也称为先进先出或严格排队方案。当每个进程就绪后，它加入就绪队列。当前正运行的进程停止执行，选择在就绪队列中存在时间最长的进程运行。该算法既可以用于作业调度，也可以用于进程调度。先来先去服务比较适合于常作业（进程），而不利于段作业（进程）。</p>
</li>
<li><p>短作业优先调度算法：短作业优先（SJF）调度算法就是会优先选择运行时间最短的进程来运行，这有助于提高系统的吞吐量</p>
</li>
<li><p>最短剩余时间优先算法：当一个新的进程到达时，把它所需要的整个运行时间与当前进程的剩余运行时间作比较。如果新的进程需要的时间更少，则挂起当前进程，运行新的进程，否则新的进程等待。</p>
</li>
<li><p>高响应比优先调度算法：每次进行进程调度时，先计算响应比优先级，然后把响应比优先级最高的进程投入运行</p>
</li>
<li><p>时间片轮转调度算法：每个进程被分配一个时间段，称为时间片（Quantum），即允许该进程在该时间段中运行，通常一个进程需要需要多个时间片才能运行完成。</p>
</li>
<li><p>最高优先级调度算法：调度程序能从就绪队列中选择最高优先级的进程进行运行</p>
</li>
<li><p>多级反馈队列调度算法</p>
<ul>
<li>「多级」表示有多个队列，每个队列优先级从高到低，同时优先级越高时间片越短。</li>
<li>「反馈」表示如果有新的进程加入优先级高的队列时，立刻停止当前正在运行的进程，转而去运行优先级高的队列；</li>
</ul>
</li>
</ol>
<h2 id="操作系统读写磁盘，影响磁盘读写时间的因素有哪些？"><a href="#操作系统读写磁盘，影响磁盘读写时间的因素有哪些？" class="headerlink" title="操作系统读写磁盘，影响磁盘读写时间的因素有哪些？"></a><strong>操作系统读写磁盘，影响磁盘读写时间的因素有哪些？</strong></h2><p>寻道时间、旋转延迟和数据传输时间</p>
<h2 id="进程间的通信机制"><a href="#进程间的通信机制" class="headerlink" title="进程间的通信机制"></a><strong>进程间的通信机制</strong></h2><p>管道（也称作共享文件）</p>
<p>消息队列（也称作消息传递）</p>
<p>共享内存（也称作共享存储）</p>
<p>信号量和 PV 操作</p>
<p>信号</p>
<p>套接字（Socket）</p>
<h2 id="线程切换要保存哪些上下文？"><a href="#线程切换要保存哪些上下文？" class="headerlink" title="线程切换要保存哪些上下文？"></a><strong>线程切换要保存哪些上下文？</strong></h2><p>当前线程Id</p>
<p>线程状态</p>
<p>堆栈</p>
<p>寄存器状态</p>
<p>其中寄存器主要包括SP PC EAX等寄存器</p>
<ul>
<li><p>SP:堆栈指针，指向当前栈的栈顶地址</p>
</li>
<li><p>PC:程序计数器，存储下一条将要执行的指令</p>
</li>
<li><p>EAX:累加寄存器，用于加法乘法的缺省寄存器</p>
</li>
</ul>
<h2 id="https加密的过程？"><a href="#https加密的过程？" class="headerlink" title="https加密的过程？"></a><strong>https加密的过程？</strong></h2><ol>
<li><p>首先TCP三次握手建立链接，这是数据传输基础，在此之上开始SSL加密；</p>
</li>
<li><p>客户端首先发送Client Hello开始SSL通信，报文中包含客户端支持的SSL版本、随机值Random1、加密算法以及密钥长度等；</p>
</li>
<li><p>服务器发送Server Hello，和客户端一样，在报文中包含SSL版本、随机值Random2以及加密组件，此后服务端将证书也发送到客户端；</p>
</li>
<li><p>此时客户端需要对服务端发送的证书进行验证，通过操作系统内置的CA证书，将服务器发送的证书的数字签名进行解密，并将证书的公钥进行相同算法的HASH与解密的数字签名解密的内容进行对比，验证证书是否合法有效，是否被劫持更换;</p>
</li>
<li><p>客户端验证证书合法，然后生成一个随机值Random3，用公钥对Random3进行加密，生成Pre-Master Key，客户端以Client Key Exchange报文将Pre-Master Key发送到服务端，此后发送Change Cipher Spec报文表示此后数据传输进行加密传输；</p>
</li>
<li><p>服务端将Pre-Master Key用自己的私钥解密为Random3，服务端发送Change Cipher Spec报文表示此后数据传输进行加密传输；</p>
</li>
</ol>
<p>这时客户端与服务端都拥有三个随机字符串，且Random3是密文传输的，是安全状态的，此时则可以使用这三个字符串进行对称加密传输。由于非对称加密慢，不能每次传输数据都进行非对称加密，所以使用非对称加密将密钥协商好然后使用对称加密进行数据传输；</p>
<p>其中2、3、5、6也被称为SSL四次握手。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/" class="post-title-link" itemprop="url">面经二</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-20 07:55:55" itemprop="dateCreated datePublished" datetime="2022-05-20T07:55:55+08:00">2022-05-20</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-23 14:23:09" itemprop="dateModified" datetime="2022-05-23T14:23:09+08:00">2022-05-23</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="算法题"><a href="#算法题" class="headerlink" title="算法题"></a><strong>算法题</strong></h2><p>手撕：报数，N个人围成一圈编号1-N，每报M个数，就淘汰第M个人，然后从M+1开始重新报M个数，问最后剩下的那个人是谁？</p>
<p>剑指offer 整数反转 easy难度</p>
<h2 id="Redis的基本数据类型有哪些？"><a href="#Redis的基本数据类型有哪些？" class="headerlink" title="Redis的基本数据类型有哪些？"></a><strong>Redis的基本数据类型有哪些？</strong></h2><ol>
<li><p>字符串</p>
</li>
<li><p>列表List</p>
</li>
<li><p>集合Set</p>
</li>
<li><p>哈希Hash</p>
</li>
<li><p>Zset</p>
</li>
</ol>
<p>还有三种不常用数据类型：geospatial、hyperloglogs、Streams</p>
<h2 id="HashSet和HashMap之间有什么关系？"><a href="#HashSet和HashMap之间有什么关系？" class="headerlink" title="HashSet和HashMap之间有什么关系？"></a><strong>HashSet和HashMap之间有什么关系？</strong></h2><p>HashSet仅仅存储对象，HashMap存储键值对</p>
<p>Hashset内部是 由Hashmap构造的，只用到了Hashmap的key。</p>
<figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HashSet</span>&lt;E&gt;</span><br><span class="line">    extends AbstractSet&lt;E&gt;</span><br><span class="line">    implements Set&lt;E&gt;, Cloneable, java.<span class="property">io</span>.<span class="property">Serializable</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> serialVersionUID = <span class="number">-5024744406713321676</span>L;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">transient</span> <span class="built_in">HashMap</span>&lt;E,<span class="built_in">Object</span>&gt; <span class="built_in">map</span>;</span><br><span class="line"> </span><br><span class="line">    <span class="comment">// Dummy value to associate with an Object in the backing Map</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="built_in">Object</span> PRESENT = <span class="keyword">new </span><span class="class title_">Object</span>();</span><br><span class="line"> </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Constructs a new, empty set; the backing &lt;tt&gt;HashMap&lt;/tt&gt; instance has</span></span><br><span class="line"><span class="comment">     * default initial capacity (16) and load factor (0.75).</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">HashSet</span>() &#123;</span><br><span class="line">	<span class="built_in">map</span> = <span class="keyword">new </span><span class="class title_">HashMap</span>&lt;E,<span class="built_in">Object</span>&gt;();</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="线程有几种状态？在Java中创建线程有哪几种方式？"><a href="#线程有几种状态？在Java中创建线程有哪几种方式？" class="headerlink" title="线程有几种状态？在Java中创建线程有哪几种方式？"></a><strong>线程有几种状态？在Java中创建线程有哪几种方式？</strong></h2><p>线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。</p>
<ol>
<li><p>通过继承Thread类创建线程</p>
</li>
<li><p>通过实现Runnable接口创建线程</p>
</li>
<li><p>使用Callable接口创建线程</p>
<h2 id="HashMap与HashTable区别"><a href="#HashMap与HashTable区别" class="headerlink" title="HashMap与HashTable区别"></a><strong>HashMap与HashTable区别</strong></h2></li>
<li><p>HashMap⽅法没有synchronized修饰，线程⾮安全，HashTable线程安全</p>
</li>
<li><p>HashMap允许key和value为null，⽽HashTable不允许</p>
</li>
</ol>
<h2 id="final作用"><a href="#final作用" class="headerlink" title="final作用"></a><strong>final作用</strong></h2><ol>
<li><p>修饰类</p>
<ul>
<li>被final修饰的类不能被继承</li>
<li>被final修饰的类所有成员方法都将被隐式修饰为final方法</li>
</ul>
</li>
<li><p>修饰变量</p>
<ul>
<li>被final修饰的变量一旦获得初始值就不可改变，不能被重新赋值</li>
<li>修饰成员变量：必须设置初始值，不能被重新赋值</li>
<li>修饰局部变量：必须设置初始值，不能被重新赋值</li>
<li>修饰基本类型变量：只能赋值一次，不能被重新赋值</li>
<li>修饰引用类型变量：引用在初始化后将永远指向一个内存地址，不可修改，但是该对象里面的信息，可以修改</li>
</ul>
</li>
<li><p>修饰方法</p>
<ul>
<li>方法不能被重写</li>
<li>方法内敛: 当调用一个方法时, 系统需要进行保存现场信息, 建立栈帧, 恢复线程等操作, 这些操作都是相对比较耗时的. 如果使用final修饰一个了一个方法a, 在其他调用方法a的类进行编译时, 方法a的代码会直接嵌入到调用a的代码块中.</li>
</ul>
</li>
</ol>
<h2 id="线程的同步方式"><a href="#线程的同步方式" class="headerlink" title="线程的同步方式"></a><strong>线程的同步方式</strong></h2><ol>
<li><p>同步方法，使用 synchronized关键字，可以修饰普通方法、静态方法，以及语句块。</p>
</li>
<li><p>同步代码块，用synchronized关键字修饰语句块。被该关键字修饰的语句块会自动被加上内置锁，从而实现同步</p>
</li>
<li><p>使用特殊域变量(volatile)实现线程同步。</p>
</li>
<li><p>使用重入锁实现线程同步，在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。</p>
</li>
<li><p>使用局部变量实现线程同步，如果使用ThreadLocal管理变量，则每一个使用该变量的线程都获得该变量的副本，副本之间相互独立，这样每一个线程都可以随意修改自己的变量副本，而不会对其他线程产生影响。  </p>
</li>
</ol>
<h2 id="路由器在哪层、路由器的转发原理、路由转发表有什么内容"><a href="#路由器在哪层、路由器的转发原理、路由转发表有什么内容" class="headerlink" title="路由器在哪层、路由器的转发原理、路由转发表有什么内容"></a><strong>路由器在哪层、路由器的转发原理、路由转发表有什么内容</strong></h2><img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/OSI.jpg" class title="OSI">
<p>由图可知，路由器在网络层</p>
<h3 id="路由器转发原理"><a href="#路由器转发原理" class="headerlink" title="路由器转发原理"></a><strong>路由器转发原理</strong></h3><p>通过路由表转发数据。</p>
<p>当分组到达一台路由器时，该路由器索引其转发表并决定该分组被指向的链路接口</p>
<h3 id="路由转发表有什么内容"><a href="#路由转发表有什么内容" class="headerlink" title="路由转发表有什么内容"></a><strong>路由转发表有什么内容</strong></h3><p>目的网络号；掩码；出接口；下一跳地址；度量值；管理距离。</p>
<h2 id="进程与线程区别"><a href="#进程与线程区别" class="headerlink" title="进程与线程区别"></a><strong>进程与线程区别</strong></h2><ol>
<li><p>根本区别：进程是操作系统资源分配的基本单位，而线程是处理器任务调度和执行的基本单位</p>
</li>
<li><p>资源开销：每个进程都有独立的代码和数据空间（程序上下文），程序之间的切换会有较大的开销；线程可以看做轻量级的进程，同一类线程共享代码和数据空间，每个线程都有自己独立的运行栈和程序计数器（PC），线程之间切换的开销小。</p>
</li>
<li><p>包含关系：如果一个进程内有多个线程，则执行过程不是一条线的，而是多条线（线程）共同完成的；线程是进程的一部分，所以线程也被称为轻权进程或者轻量级进程。</p>
</li>
<li><p>内存分配：同一进程的线程共享本进程的地址空间和资源，而进程之间的地址空间和资源是相互独立的</p>
</li>
<li><p>影响关系：一个进程崩溃后，在保护模式下不会对其他进程产生影响，但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。</p>
</li>
<li><p>执行过程：每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制，两者均可并发执行</p>
</li>
</ol>
<h2 id="怎么解决死锁"><a href="#怎么解决死锁" class="headerlink" title="怎么解决死锁"></a><strong>怎么解决死锁</strong></h2><p>死锁产生的四个必要条件：</p>
<ol>
<li><p>互斥使用，即当资源被一个线程使用(占有)时，别的线程不能使用</p>
</li>
<li><p>不可抢占，资源请求者不能强制从资源占有者手中夺取资源，资源只能由资源占有者主动释放。</p>
</li>
<li><p>请求和保持，即当资源请求者在请求其他的资源的同时保持对原有资源的占有。</p>
</li>
<li><p>循环等待，即存在一个等待队列：P1占有P2的资源，P2占有P3的资源，P3占有P1的资源。这样就形成了一个等待环路。</p>
</li>
</ol>
<p>当上述四个条件都成立的时候，便形成死锁。当然，死锁的情况下如果打破上述任何一个条件，便可让死锁消失。</p>
<p>解决死锁问题的方法是：一种是用synchronized，一种是用Lock显式锁实现。</p>
<h2 id="二叉树遍历-4种、morris遍历具体、层次遍历"><a href="#二叉树遍历-4种、morris遍历具体、层次遍历" class="headerlink" title="二叉树遍历 4种、morris遍历具体、层次遍历"></a><strong>二叉树遍历 4种、morris遍历具体、层次遍历</strong></h2><p>二叉树遍历方式：先序遍历、中序遍历、后序遍历、层序遍历</p>
<p><strong>morris遍历</strong></p>
<p>假设来到当前节点cur，开始时cur来到头节点位置</p>
<ol>
<li><p>如果cur没有左孩子，cur向右移动(cur = cur.right)</p>
</li>
<li><p>如果cur有左孩子，找到左子树最右的节点mostRight</p>
<ol>
<li>如果mostRight的右指针指向空，让其指向cur，然后cur向左移动(cur = cur.left)</li>
<li>如果mostRight的右指针指向cur,让其指向null,然后cur向右移动(cur = cur.right)</li>
</ol>
</li>
<li>cur为空时停止遍历<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">morris</span><span class="params">(Node head)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (head == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">cur</span> <span class="operator">=</span> head;</span><br><span class="line">    <span class="type">Node</span> <span class="variable">mostRight</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">while</span> (cur != <span class="literal">null</span>) &#123;</span><br><span class="line">        mostRight = cur.left;</span><br><span class="line">        <span class="keyword">if</span> (mostRight != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (mostRight.right != <span class="literal">null</span> &amp;&amp; mostRight.right != cur) &#123;</span><br><span class="line">                mostRight = mostRight.right;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (mostRight.right == <span class="literal">null</span>) &#123;</span><br><span class="line">                mostRight.right = cur;</span><br><span class="line">                cur = cur.left;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                mostRight.right = <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        cur = cur.right;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="对象头、markword"><a href="#对象头、markword" class="headerlink" title="对象头、markword"></a><strong>对象头、markword</strong></h2><img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E5%AF%B9%E8%B1%A1%E7%BB%93%E6%9E%84.png" class title="对象结构">
<p>对象的几个部分的作用：</p>
<ol>
<li><p>对象头中的Mark Word（标记字）主要用来表示对象的线程锁状态，另外还可以用来配合GC、存放该对象的hashCode；</p>
</li>
<li><p>Klass Word是一个指向方法区中Class信息的指针，意味着该对象可随时知道自己是哪个Class的实例；</p>
</li>
<li><p>数组长度也是占用64位（8字节）的空间，这是可选的，只有当本对象是一个数组对象时才会有这个部分；</p>
</li>
<li><p>对象体是用于保存对象属性和值的主体部分，占用内存空间取决于对象的属性数量和类型；</p>
</li>
<li><p>对齐字是为了减少堆内存的碎片空间</p>
</li>
</ol>
<p>Mark Word表达的锁状态含义</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/markWord.png" class title="markWord">
<h2 id="类加载机制、准备和初始化在干什么"><a href="#类加载机制、准备和初始化在干什么" class="headerlink" title="类加载机制、准备和初始化在干什么"></a><strong>类加载机制、准备和初始化在干什么</strong></h2><p>java虚拟机将编译后的class文件加载到内存中，进行校验、转换、解析和初始化，到最终的使用。这就是java类加载机制；而 JVM 虚拟机执行 class 字节码的过程可以分为七个阶段：<strong>加载、验证、准备、解析、初始化、使用、卸载</strong>。</p>
<ol>
<li><p>编写Java文件</p>
</li>
<li><p>编译器编译Java源文件生成.class文件</p>
</li>
<li><p>JVM的类装载器装载.class文件， 在堆中生成一个代表这个类的Class对象，作为方法区中这些数据的访问入口 ，并为变量分配相应的内存（五步：加载、验证、准备、解析、初始化）</p>
</li>
<li><p>执行引擎执行</p>
</li>
</ol>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E6%89%A7%E8%A1%8C.png" class title="执行引擎执行">
<ol>
<li><p><strong>加载</strong></p>
<ol>
<li>通过一个类的全限定名来获取其定义的二进制字节流</li>
<li>将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构</li>
<li>在堆中生成一个代表这个类的Class对象，作为方法区中这些数据的访问入口。</li>
</ol>
</li>
<li><p><strong>验证</strong>:当代码数据被加载到内存中后，虚拟机就会对代码数据进行校验，看看这份代码是不是真的按照JVM规范去写的</p>
</li>
<li><p><strong>准备</strong>:备阶段主要为类变量分配内存并设置初始值,这些内存都在方法区分配</p>
<ol>
<li>类变量（static）会分配内存，但是实例变量不会，实例变量主要随着对象的实例化一块分配到java堆中，</li>
<li>这里的初始值指的是数据类型默认值，而不是代码中被显示赋予的值，比如public static int value = 1; //在这里准备阶段过后的value值为0，而不是1。赋值为1的动作在初始化阶段。</li>
</ol>
</li>
<li><p><strong>解析</strong>：解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程</p>
<p> 符号引用：以一组符号来描述所引用的目标，可以是任何形式的字面量，只要是能无歧义的定位到目标就好，就好比在班级中，老师可以用张三来代表你，也可以用你的学号来代表你，但无论任何方式这些都只是一个代号（符号），这个代号指向你（符号引用）</p>
<p> 直接引用：直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关，不同的虚拟机直接引用一般不同。</p>
<p> 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。</p>
</li>
<li><p><strong>初始化</strong>：简单来说就是执行类构造器<clinit>()方法的过程。</clinit></p>
<p> 到了初始化阶段，用户定义的 Java 程序代码才真正开始执行。</p>
<p> 我们知道，在准备阶段已经为类变量赋过一次值。在初始化阶端，程序员可以根据自己的需求来赋值了。</p>
<ol>
<li><p>假如这个类还没有被加载和连接，则程序先加载并连接该类</p>
</li>
<li><p>假如该类的直接父类还没有被初始化，则先初始化其直接父类</p>
</li>
<li><p>假如类中有初始化语句，则系统依次执行这些初始化语句</p>
<p>类初始化时机：只有当对类的主动使用的时候才会导致类的初始化</p>
<p>类的主动使用包括以下六种：</p>
</li>
<li><p>创建类的实例，也就是new的方式访问某个类或接口的静态变量</p>
</li>
<li><p>对该静态变量赋值调用类的静态方法</p>
</li>
<li><p>反射（如 Class.forName(“com.shengsiyuan.Test”)）</p>
</li>
<li><p>初始化某个类的子类，则其父类也会被初始化</p>
</li>
<li><p>Java虚拟机启动时被标明为启动类的类（JavaTest）</p>
</li>
<li><p>直接使用 java.exe命令来运行某个主类</p>
</li>
</ol>
</li>
</ol>
<h2 id="集合类的顶级接口"><a href="#集合类的顶级接口" class="headerlink" title="集合类的顶级接口"></a><strong>集合类的顶级接口</strong></h2><p>Collection接口</p>
<h2 id="synchronized的改进"><a href="#synchronized的改进" class="headerlink" title="synchronized的改进"></a><strong>synchronized的改进</strong></h2><ol>
<li><p><strong>锁膨胀</strong>：所谓的锁膨胀是指 synchronized 从无锁升级到偏向锁，再到轻量级锁，最后到重量级锁的过程，它叫做锁膨胀也叫做锁升级。</p>
</li>
<li><p><strong>锁消除</strong>：锁消除指的是在某些情况下，JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性，就会将这段代码所属的同步锁消除掉，从而到底提高程序性能的目的。</p>
<p> 锁消除的依据是逃逸分析的数据支持，如 StringBuffer 的 append() 方法，或 Vector 的 add() 方法，在很多情况下是可以进行锁消除的,比如以下这段代码：</p>
 <figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="keyword">method</span><span class="params">()</span> <span class="comment">&#123;</span></span><br><span class="line"><span class="comment">    StringBuffer sb = new StringBuffer();</span></span><br><span class="line"><span class="comment">    for (int i = 0; i &lt; 10; i++) &#123;</span></span><br><span class="line"><span class="comment">        sb.append(&quot;i:&quot; + i);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line">    <span class="title function_">return</span> <span class="title function_">sb</span>.<span class="title function_">toString</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p> 原因是 StringBuffer 的变量属于一个局部变量，并且不会从该方法中逃逸出去，所以此时我们就可以使用锁消除（不加锁）来加速程序的运行。</p>
</li>
<li><p><strong>锁粗化</strong>：锁粗化是指，将多个连续的加锁、解锁操作连接在一起，扩展成一个范围更大的锁。<br> 例如：</p>
 <figure class="highlight oxygene"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="keyword">method</span><span class="params">()</span> <span class="comment">&#123;</span></span><br><span class="line"><span class="comment">    StringBuffer sb = new StringBuffer();</span></span><br><span class="line"><span class="comment">    for (int i = 0; i &lt; 10; i++) &#123;</span></span><br><span class="line"><span class="comment">        sb.append(&quot;i:&quot; + i);</span></span><br><span class="line"><span class="comment">    &#125;</span></span><br><span class="line">    <span class="title function_">return</span> <span class="title function_">sb</span>.<span class="title function_">toString</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p> 如果在 for 循环中定义锁，那么锁的范围很小，但每次 for 循环都需要进行加锁和释放锁的操作，性能是很低的；但如果我们直接在 for 循环的外层加一把锁，那么对于同一个对象操作这段代码的性能就会提高很多</p>
<ul>
<li>锁粗化的作用：如果检测到同一个对象执行了连续的加锁和解锁的操作，则会将这一系列操作合并成一个更大的锁，从而提升程序的执行效率。</li>
</ul>
</li>
<li><p><strong>自适应自旋锁</strong>:自适应自旋锁是指，线程自旋的次数不再是固定的值，而是一个动态改变的值，这个值会根据前一次自旋获取锁的状态来决定此次自旋的次数,简单来说，如果线程自旋成功了，则下次自旋的次数会增多，如果失败，下次自旋的次数会减少。</p>
<ul>
<li>自旋锁是指通过自身循环，尝试获取锁的一种方式</li>
<li>自旋锁优点在于它避免一些线程的挂起和恢复操作，因为挂起线程和恢复线程都需要从用户态转入内核态，这个过程是比较慢的，所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。</li>
<li>自适应自旋锁避免了资源的浪费，让sychronized效率更高了。</li>
</ul>
</li>
</ol>
<h2 id="wait-notify"><a href="#wait-notify" class="headerlink" title="wait-notify"></a><strong>wait-notify</strong></h2><p><strong>wait</strong>:wait()方法是Object 类的方法，它的作用是使当前执行wait()方法的线程等待，在wait()所在的代码行处暂停执行，并释放锁，直到接到通知或中断。</p>
<p><strong>notify</strong>:notify()方法用来通知那些可能等待该锁的其他线程，如果有多个线程等待，则按照执行wait方法的顺序发出一次性通知（一次只能通知一个！），使得等待排在第一顺序的线程获得锁。需要说明的是，执行notify方法后，当前线程并不会立即释放锁，要等到程序执行完，即退出synchronized同步区域后。</p>
<p><strong>总结</strong>：wait 方法使线程暂停运行，而notify 方法通知暂停的线程继续运行。</p>
<h2 id="MySQL"><a href="#MySQL" class="headerlink" title="MySQL"></a><strong>MySQL</strong></h2><h3 id="索引下推是什么？"><a href="#索引下推是什么？" class="headerlink" title="索引下推是什么？"></a><strong>索引下推是什么？</strong></h3><p>什么是回表操作?</p>
<p>当我们在非主键索引上查找一行数据的时候，此时的查找方式是先搜索非主键索引树，拿到对应的主键值，再到主键索引树上查找对应的行数据。这种操作就叫作回表操作。</p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">在这里有张用户表 <span class="keyword">user</span>，记录着用户的姓名，性别，身高，年龄等信息。表中 id 是自增主键，(<span class="type">name</span>,sex) 是联合索引。在这里用 <span class="number">1</span> 表示男，<span class="number">2</span> 表示女。现在需要查找所有姓王的男性信息。</span><br><span class="line"><span class="keyword">select</span> * <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> <span class="type">name</span> <span class="keyword">like</span> <span class="string">&#x27;王%&#x27;</span> <span class="keyword">and</span> sex = <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E6%9C%AA%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png" class title="未索引下推">
<p>在未使用索引下推时，根据联合索引最左前缀原则，我们在非主键索引树上找到第一个满足条件的值时，通过叶子节点记录的主键值再回到主键索引树上查找到对应的行数据，再对比是否为当前所要查找的性别。</p>
<p>这样增加了回表的次数</p>
<p>当使用了索引下推后：</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8.png" class title="使用索引下推">
<p>所以本质来说，索引下推就是只有符合条件再进行回表，对索引中包含的字段先进行判断，不符合条件的跳过。减少了不必要的回表操作。</p>
<p><strong>总结</strong>：索引下推主要是减少了不必要的回表操作。对于查找出来的数据，先过滤掉不符合条件的，其余的再去主键索引树上查找。</p>
<h3 id="覆盖索引是什么？"><a href="#覆盖索引是什么？" class="headerlink" title="覆盖索引是什么？"></a><strong>覆盖索引是什么？</strong></h3><p>MySQL 执行计划 explain 结果里的 key 有使用索引，如果 select 后面查询的字段都可以从这个索引的树中获取，这种情况一般可以说是用到了覆盖索引，extra 里一般都有 using index；覆盖索引一般针对的是辅助索引，整个査询结果只通过辅助索引就能拿到结果，不需要通过辅助索引树找到主键，再通过主键去主键索引树里获取其它字段值。</p>
<p>简单说就是只需要查询辅助索引，就可以获取到想要查询的值，并不需要再回表去主键索引处查询</p>
<h2 id="yong-GC-的发生时间？发生频繁，发生时间长的原因和解决方案？"><a href="#yong-GC-的发生时间？发生频繁，发生时间长的原因和解决方案？" class="headerlink" title="yong GC 的发生时间？发生频繁，发生时间长的原因和解决方案？"></a><strong>yong GC 的发生时间？发生频繁，发生时间长的原因和解决方案？</strong></h2><p>eden区满了会触发young GC</p>
<p><strong>发生频繁，发生时间长的原因以及解决方案</strong></p>
<ol>
<li>JVM参数设置不当：调整JVM参数</li>
</ol>
<p>2.代码不合理：更改代码</p>
<h2 id="CMS怎么标记垃圾的？三色标记法？G1的区别？"><a href="#CMS怎么标记垃圾的？三色标记法？G1的区别？" class="headerlink" title="CMS怎么标记垃圾的？三色标记法？G1的区别？"></a><strong>CMS怎么标记垃圾的？三色标记法？G1的区别？</strong></h2><p>CMS是使用三色标记法来标记垃圾的</p>
<ol>
<li><p>刚开始，所有的对象都是白色，没有被访问。</p>
</li>
<li><p>将GC Roots直接关联的对象置为灰色。</p>
</li>
<li><p>遍历灰色对象的所有引用，遍历后灰色对象本身置为黑色，引用置为灰色。</p>
</li>
<li><p>重复步骤3，直到没有灰色对象为止。</p>
</li>
<li><p>结束时，黑色对象存活，白色对象回收。</p>
</li>
</ol>
<h2 id="为什么GC要STW-stop-the-world-？"><a href="#为什么GC要STW-stop-the-world-？" class="headerlink" title="为什么GC要STW(stop the world)？"></a><strong>为什么GC要STW(stop the world)？</strong></h2><p>首先，如果不暂停用户线程，就意味着期间会不断有垃圾产生，永远也清理不干净。</p>
<p>其次，用户线程的运行必然会导致对象的引用关系发生改变，这就会导致两种情况：漏标和错标。</p>
<p>这也是三色标记算法存在的问题</p>
<h3 id="漏标是什么？"><a href="#漏标是什么？" class="headerlink" title="漏标是什么？"></a><strong>漏标是什么？</strong></h3><p>某个状态下，黑色-&gt;灰色-&gt;白色</p>
<p>如果一切顺利，不发生任何引用变化，GC线程顺着灰色的引用向下扫描,最后都变成黑色，都是存活对象</p>
<p>但是如果出现了这样一个状况，在扫描到灰色的时候，还没有扫描到这个白色对象，此时，黑色对象引用了这个白色对象，而灰色对象指向了别的对象，或者干脆指向了null，也就是取消了对白色对象的引用</p>
<p>那么我们会发现一个问题，根据三色标记规则，GC会认为,黑色对象是本身已经被扫描过，并且它所有指向的引用都已经被扫描过，所以不会再去扫描它有哪些引用指向了哪些对象，然后,灰色对象因为取消了对白色对象的引用，所以后面GC开始扫描所有灰色对象的引用时候，也不会再扫描到白色对象。</p>
<p><strong>最后结果就是,白色对象直到本次标记扫描结束,也是白色,根据三色标记规则,认为它是垃圾,被清理掉</strong></p>
<h3 id="错标是什么？"><a href="#错标是什么？" class="headerlink" title="错标是什么？"></a><strong>错标是什么？</strong></h3><p>假设GC线程已经遍历到B，此时用户线程执行了一下以下两个操作</p>
<figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">//B到D的引用被切断</span><br><span class="line">B.D<span class="operator">=</span>null<span class="comment">;	</span></span><br><span class="line">//A到D的引用被建立</span><br><span class="line">A.xx<span class="operator">=</span>D<span class="comment">;	</span></span><br></pre></td></tr></table></figure>
<p>B到D的引用被切断，且A到D的引用被建立。</p>
<p>此时GC线程继续工作，由于B不再引用D了，尽管A又引用了D，但是因为A已经标记为黑色，GC不会再遍历A了，所以D会被标记为白色，最后被当做垃圾回收。</p>
<p>可以看到错标的结果比漏表严重的多，浮动垃圾可以下次GC清理，而把不该回收的对象回收掉，将会造成程序运行错误。</p>
<h2 id="手写多线程生产者消费者（阻塞队列实现、信号量实现）"><a href="#手写多线程生产者消费者（阻塞队列实现、信号量实现）" class="headerlink" title="手写多线程生产者消费者（阻塞队列实现、信号量实现）"></a><strong>手写多线程生产者消费者（阻塞队列实现、信号量实现）</strong></h2><h2 id="java面向对象特点"><a href="#java面向对象特点" class="headerlink" title="java面向对象特点"></a><strong>java面向对象特点</strong></h2><p>抽象、封装、继承、多态</p>
<h2 id="HashMap啥时候扩容，为什么扩容？"><a href="#HashMap啥时候扩容，为什么扩容？" class="headerlink" title="HashMap啥时候扩容，为什么扩容？"></a><strong>HashMap啥时候扩容，为什么扩容？</strong></h2><p>当hashmap中的元素个数size超过数组长度*loadFactor时，就会进行数组扩容,loadFactor的默认值为0.75</p>
<p>为什么扩容：因为要保持HashMap的理想情况下的时间复杂度O(1)</p>
<h2 id="Reentrantlock加锁的源码"><a href="#Reentrantlock加锁的源码" class="headerlink" title="Reentrantlock加锁的源码"></a><strong>Reentrantlock加锁的源码</strong></h2><h3 id="非公平锁"><a href="#非公平锁" class="headerlink" title="非公平锁"></a><strong>非公平锁</strong></h3><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="comment">//直接CAS加锁看能否成功</span></span><br><span class="line">    if (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        <span class="comment">//成功后设置当前线程</span></span><br><span class="line">        <span class="built_in">setExclusiveOwnerThread</span>(Thread.currentThread());</span><br><span class="line">    else</span><br><span class="line">        <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面直接先尝试CAS,如果成功后把占用锁的线程设置成自己,加锁失败则进入acquire方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public final void acquire(<span class="built_in">int</span> arg) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">try</span><span class="constructor">Acquire(<span class="params">arg</span>)</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">        </span>acquire<span class="constructor">Queued(<span class="params">addWaiter</span>(Node.EXCLUSIVE)</span>, arg))</span><br><span class="line">        <span class="comment">//补偿中断状态</span></span><br><span class="line">        self<span class="constructor">Interrupt()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>首先来看看tryAcquire方法里面做了什么,点进去会发现最终的实现是nonfairTryAcquire方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">final boolean nonfair<span class="constructor">TryAcquire(<span class="params">int</span> <span class="params">acquires</span>)</span> &#123;</span><br><span class="line">    final Thread current = <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>;</span><br><span class="line">    <span class="comment">//获取锁状态</span></span><br><span class="line">    <span class="built_in">int</span> c = get<span class="constructor">State()</span>;</span><br><span class="line">    <span class="keyword">if</span> (c<span class="operator"> == </span><span class="number">0</span>) &#123;<span class="comment">//直接尝试加锁,这里和外层的尝试加锁是一样的,只是再尝试一次</span></span><br><span class="line">        <span class="keyword">if</span> (compare<span class="constructor">AndSetState(0, <span class="params">acquires</span>)</span>) &#123;</span><br><span class="line">            set<span class="constructor">ExclusiveOwnerThread(<span class="params">current</span>)</span>;</span><br><span class="line">            <span class="comment">//加锁成功的话就直接返回true了,外层的acquire方法其实就直接结束了</span></span><br><span class="line">            return <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//判断是否是当前线程持有锁</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current<span class="operator"> == </span>get<span class="constructor">ExclusiveOwnerThread()</span>) &#123;</span><br><span class="line">        <span class="comment">//此处可以说明是可重入锁</span></span><br><span class="line">        <span class="built_in">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line">            throw <span class="keyword">new</span> <span class="constructor">Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>)</span>;</span><br><span class="line">        set<span class="constructor">State(<span class="params">nextc</span>)</span>;</span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//返回false说明需要放入队列</span></span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以发现tryAcquire做了两件事: 1,再次尝试加锁 2,持有锁的是否是自己 如果return true的话,方法就直接结束了,如果reture false的话,则会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)</p>
<p>那我们先来看下addWaiter方法<br><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">private <span class="keyword">Node</span> <span class="title">addWaiter</span>(<span class="keyword">Node</span> <span class="title">mode</span>) &#123;</span><br><span class="line">    //此时mode为null</span><br><span class="line">    <span class="keyword">Node</span> <span class="title">node</span> = new <span class="keyword">Node</span><span class="title">(Thread</span>.currentThread(), mode);</span><br><span class="line">    // Try the fast path of enq; backup to full enq on failure</span><br><span class="line">    //tail是结尾的指针,赋值给pred,当第一个线程进来的时候,tail为null</span><br><span class="line">    <span class="keyword">Node</span> <span class="title">pred</span> = tail;</span><br><span class="line">    //此处判断pred是否为null就是判断tail是否为null,也就是判断队尾是否有节点</span><br><span class="line"></span><br><span class="line">    //如果不为空,直接把当前节点放到tail的后面</span><br><span class="line">    if (pred != null) &#123;</span><br><span class="line">        //把当前节点放在tail后面</span><br><span class="line">        node.prev = pred;</span><br><span class="line">        //把当前节点设置为tail指针,其实就是把当前节点设置成最后一个</span><br><span class="line">        if (compareAndSetTail(pred, <span class="keyword">node</span><span class="title">)) &#123;//CAS</span>确保入队时是原子操作</span><br><span class="line">            //当前<span class="keyword">node</span><span class="title">成为新的队尾</span></span><br><span class="line"><span class="title">            pred</span>.next = <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">            return</span> <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">        &#125;</span></span><br><span class="line"><span class="title">    &#125;</span></span><br><span class="line"><span class="title">    //如果为空,执行下面的方法</span></span><br><span class="line"><span class="title">    enq</span>(<span class="keyword">node</span><span class="title">);</span></span><br><span class="line"><span class="title">    return</span> <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">&#125;</span></span><br><span class="line"><span class="title">private</span> <span class="keyword">Node</span> <span class="title">enq</span>(final <span class="keyword">Node</span> <span class="title">node</span>) &#123;</span><br><span class="line">    //死循环初始化队列</span><br><span class="line">    for (;;) &#123;</span><br><span class="line">        <span class="keyword">Node</span> <span class="title">t</span> = tail;</span><br><span class="line">        if (t == null) &#123; // Must initialize</span><br><span class="line">            //死循环的第一次会初始化tail和head,刚开始的时候其实tail和head是一样的</span><br><span class="line">            if (compareAndSetHead(new <span class="keyword">Node</span><span class="title">()))</span></span><br><span class="line"><span class="title">                tail</span> = head;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            //这里的代码和addWaiter中的是一样的</span><br><span class="line">            node.prev = t;</span><br><span class="line">            //死循环的第二次会把当前<span class="keyword">node</span><span class="title">接到之前的tail</span>位置后面</span><br><span class="line">            if (compareAndSetTail(t, <span class="keyword">node</span><span class="title">)) &#123;</span></span><br><span class="line"><span class="title">                t</span>.next = <span class="keyword">node</span><span class="title">;</span></span><br><span class="line"><span class="title">                return</span> t;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>可以发现addWaiter方法的作用其实就是把当前node设置到tail节点的后面,如果tail节点为空的话则执行enq方法初始化head和tail节点,无论结果如何,addWaiter都会返回当前的node</p>
<p>然后下一步进入acquireQueued方法</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">final boolean acquire<span class="constructor">Queued(<span class="params">final</span> Node <span class="params">node</span>, <span class="params">int</span> <span class="params">arg</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//标志1</span></span><br><span class="line">    boolean failed = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//标志2</span></span><br><span class="line">        boolean interrupted = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">//死循环</span></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="comment">//找到当前node的prev节点</span></span><br><span class="line">            final Node p = node.predecessor<span class="literal">()</span>;</span><br><span class="line">            <span class="comment">//我们可以把head当做当前持有锁的节点,如果前一个是head,则开始重新获取锁</span></span><br><span class="line">            <span class="keyword">if</span> (p<span class="operator"> == </span>head<span class="operator"> &amp;&amp; </span><span class="keyword">try</span><span class="constructor">Acquire(<span class="params">arg</span>)</span>) &#123;</span><br><span class="line">                <span class="comment">//如果此时获取锁成功,则把当前节点放到head位置</span></span><br><span class="line">                set<span class="constructor">Head(<span class="params">node</span>)</span>;</span><br><span class="line">                <span class="comment">//node代替了之前的head,所以把之前的head置为null</span></span><br><span class="line">                p.next = null; <span class="comment">// help GC</span></span><br><span class="line">                failed = <span class="literal">false</span>;</span><br><span class="line">                <span class="comment">//加锁成功</span></span><br><span class="line">                return interrupted;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//有两种情况会到这</span></span><br><span class="line">            <span class="comment">//1,前一个不是head,说明还有其他兄弟在排队</span></span><br><span class="line">            <span class="comment">//2,前一个是head, 但是head还没有释放锁,加锁失败了</span></span><br><span class="line">            <span class="keyword">if</span> (should<span class="constructor">ParkAfterFailedAcquire(<span class="params">p</span>, <span class="params">node</span>)</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">                </span>park<span class="constructor">AndCheckInterrupt()</span>)</span><br><span class="line">                interrupted = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; finally &#123;</span><br><span class="line">        <span class="keyword">if</span> (failed)</span><br><span class="line">            cancel<span class="constructor">Acquire(<span class="params">node</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>有两种情况会去执行shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法</p>
<p>1,前一个不是head,说明还有其他兄弟在排队</p>
<p>2,前一个是head, 但是head还没有释放锁,加锁失败了</p>
<p>通过方法名称可以知道shouldParkAfterFailedAcquire是检测当前线程是否有挂起的资格</p>
<p>parkAndCheckInterrupt则是说明在shouldParkAfterFailedAcquire返回true的情况下,挂起线程</p>
<p>首先来看下shouldParkAfterFailedAcquire方法<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> static boolean should<span class="constructor">ParkAfterFailedAcquire(Node <span class="params">pred</span>, Node <span class="params">node</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//上一个节点的状态</span></span><br><span class="line">    <span class="built_in">int</span> ws = pred.waitStatus;</span><br><span class="line">    <span class="comment">//如果是SIGNAL,直接返回true</span></span><br><span class="line">    <span class="keyword">if</span> (ws<span class="operator"> == </span>Node.SIGNAL)</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * This node has already set status asking a release</span></span><br><span class="line"><span class="comment">            * to signal it, so it can safely park.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">if</span> (ws &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * Predecessor was cancelled. Skip over predecessors and</span></span><br><span class="line"><span class="comment">            * indicate retry.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            <span class="comment">//如果是CANCELLED状态,循环往队列前面找,直到找到一个SIGNAL的节点,然后把当前节点放到SIGNAL节点的后面</span></span><br><span class="line">            node.prev = pred = pred.prev;</span><br><span class="line">        &#125; <span class="keyword">while</span> (pred.waitStatus &gt; <span class="number">0</span>);</span><br><span class="line">        pred.next = node;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">            * waitStatus must be 0 or PROPAGATE.  Indicate that we</span></span><br><span class="line"><span class="comment">            * need a signal, but don&#x27;t park yet.  Caller will need to</span></span><br><span class="line"><span class="comment">            * retry to make sure it cannot acquire before parking.</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">        <span class="comment">//把之前的节点设置为SIGNAL</span></span><br><span class="line">        compare<span class="constructor">AndSetWaitStatus(<span class="params">pred</span>, <span class="params">ws</span>, Node.SIGNAL)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>可以发现shouldParkAfterFailedAcquire方法的作用就是保证当前node节点的prev节点的状态必须是SIGNAL,当满足这个条件后才会去执行parkAndCheckInterrupt方法,否则就会进入下一次死循环直到保证</p>
<p>prev节点的状态是SIGNAL,因为只有当prev节点是SIGNAL状态时,后续才会去唤醒下一个节点,当前节点才敢把自己挂起</p>
<p>然后我们看下parkAndCheckInterrupt方法<br><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="function"><span class="keyword">boolean</span> <span class="title">parkAndCheckInterrupt</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">//阻塞当前线程</span></span><br><span class="line">    LockSupport.park(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">//这里返回true后会清除掉状态</span></span><br><span class="line">    <span class="function"><span class="keyword">return</span> Thread.<span class="title">interrupted</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>这里就是直接挂起自己,等待head节点把自己唤醒,到这里的话,非公平锁的加锁过程就结束了</p>
<p>对state变量进行CAS,失败的话则新建一个node对象,把自己放到head的后面,然后此时又拿不到锁,没啥事做,那就自闭吧,把线程阻塞起来,防止cpu空转.</p>
<h3 id="公平锁是怎么实现公平的呢"><a href="#公平锁是怎么实现公平的呢" class="headerlink" title="公平锁是怎么实现公平的呢?"></a><strong>公平锁是怎么实现公平的呢?</strong></h3><p>我们看看公平锁和非公平锁两者源码的区别</p>
<p><strong>区别1</strong><br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//非公平</span></span><br><span class="line"></span><br><span class="line">final void <span class="built_in">lock</span>() &#123;</span><br><span class="line">    <span class="comment">//直接CAS加锁看能否成功</span></span><br><span class="line">    if (compareAndSetState(<span class="number">0</span>, <span class="number">1</span>))</span><br><span class="line">        <span class="comment">//成功后设置当前线程</span></span><br><span class="line">        <span class="built_in">setExclusiveOwnerThread</span>(Thread.currentThread());</span><br><span class="line">    else</span><br><span class="line">        <span class="built_in">acquire</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>上面我们对比了lock方法 如果是非公平锁,新进来一个线程会直接去尝试加锁,根本不会排队 公平锁则直接进入了acquire方法</p>
<p><strong>区别二</strong><br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">protected final boolean <span class="keyword">try</span><span class="constructor">Acquire(<span class="params">int</span> <span class="params">acquires</span>)</span> &#123;</span><br><span class="line">    final Thread current = <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>;</span><br><span class="line">    <span class="built_in">int</span> c = get<span class="constructor">State()</span>;</span><br><span class="line">    <span class="keyword">if</span> (c<span class="operator"> == </span><span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!has<span class="constructor">QueuedPredecessors()</span><span class="operator"> &amp;&amp;</span></span><br><span class="line"><span class="operator">            </span>compare<span class="constructor">AndSetState(0, <span class="params">acquires</span>)</span>) &#123;</span><br><span class="line">            set<span class="constructor">ExclusiveOwnerThread(<span class="params">current</span>)</span>;</span><br><span class="line">            return <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (current<span class="operator"> == </span>get<span class="constructor">ExclusiveOwnerThread()</span>) &#123;</span><br><span class="line">        <span class="built_in">int</span> nextc = c + acquires;</span><br><span class="line">        <span class="keyword">if</span> (nextc &lt; <span class="number">0</span>)</span><br><span class="line">            throw <span class="keyword">new</span> <span class="constructor">Error(<span class="string">&quot;Maximum lock count exceeded&quot;</span>)</span>;</span><br><span class="line">        set<span class="constructor">State(<span class="params">nextc</span>)</span>;</span><br><span class="line">        return <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    return <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>上面是公平锁的tryAcquire方法,我们可以看到这个和非公平锁的区别只是多了一个hasQueuedPredecessors()判断方法,同样,我们先看看这个方法是干啥的<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">hasQueuedPredecessors</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// The correctness of this depends on head being initialized</span></span><br><span class="line">    <span class="comment">// before tail and on head.next being accurate if the current</span></span><br><span class="line">    <span class="comment">// thread is first in queue.</span></span><br><span class="line">    <span class="comment">//判断队列中是否有优先级更高的等待线程</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">t</span> <span class="operator">=</span> tail; <span class="comment">// Read fields in reverse initialization order</span></span><br><span class="line">    <span class="type">Node</span> <span class="variable">h</span> <span class="operator">=</span> head;</span><br><span class="line">    Node s;</span><br><span class="line">    <span class="comment">//这里如果返回false 才会去CAS抢锁,否则就去排队</span></span><br><span class="line">    <span class="keyword">return</span> h != t &amp;&amp;</span><br><span class="line">        ((s = h.next) == <span class="literal">null</span> || s.thread != Thread.currentThread());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>总结一下,实现公平锁就是通过hasQueuedPredecessors方法来判断是否有高优先级的线程,而不是像非公平锁一样直接去抢锁.</p>
<p><strong>总结一下加锁流程</strong><br><figure class="highlight xquery"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">线程尝试加锁,加锁失败后会进入AQS队列,队列第一次会初始化一<span class="built_in">个head</span>节点,此<span class="built_in">时head</span>节点的内部线程是null,然后第一个加入队列的线程节点thread1会接在初始化后<span class="built_in">的head</span>节点后面,然后thread1会park自己,也就是说,除<span class="built_in">了head</span>节点外,队列中其他的节点都会park自己,然后持有锁的线程释放锁后,会唤<span class="built_in">醒head</span>节点后面的第一个节点,此时也就是thread1会被唤醒,thread获取到锁后会把自己置<span class="built_in">为head</span>,并且把自身的thread置为null,因为拿到锁后会setExclusiveOwnerThread(current);所以没有必要再持有线程.</span><br><span class="line">除了第一次初始化<span class="built_in">的head</span>外,所有<span class="built_in">的head</span>节点都是已经拿到锁的节点</span><br></pre></td></tr></table></figure></p>
<h2 id="Spring事务的实现方式及底层原理"><a href="#Spring事务的实现方式及底层原理" class="headerlink" title="Spring事务的实现方式及底层原理"></a><strong>Spring事务的实现方式及底层原理</strong></h2><ol>
<li><p>spring事务的实现方式</p>
<ul>
<li>spring框架提供了两种事务实现方式：编程式事务、声明式事务</li>
<li>编程式事务：在代码中进行事务控制。优点：精度高。缺点：代码耦合度高</li>
<li>声明式事务：通过@Transactional注解实现事务控制</li>
</ul>
</li>
<li><p>spring事务的底层原理</p>
<ul>
<li>Spring的事务管理是通过AOP代理实现的，对被代理对象的每个方法进行拦截，在方法执行前启动事务，在方法执行完成后根据是否有异常及异常的类型进行提交或回滚。</li>
</ul>
</li>
</ol>
<h2 id="Redis的集群模式，选举和脑裂"><a href="#Redis的集群模式，选举和脑裂" class="headerlink" title="Redis的集群模式，选举和脑裂"></a><strong>Redis的集群模式，选举和脑裂</strong></h2><p>集群模式：主从模式、哨兵模式、Cluster模式</p>
<p>脑裂：指在主从集群中，同时有两个主节点，它们都能接收写请求</p>
<p>那么什么时候会出现这种情况呢？</p>
<p>就是如果当前主库突然出现暂时性 “失联”，而并不是真的发生了故障，此时监听的哨兵会自动启动主从切换机制。当这个原始的主库从假故障中恢复后，又开始处理请求，但是哨兵已经选出了新的主库，这样一来，旧的主库和新主库就会同时存在，这就是脑裂现象。</p>
<p><strong>脑裂有什么影响？</strong></p>
<p>脑裂最直接的影响，就是客户端不知道应该往哪个主节点写入数据，结果就是不同的客户端会往不同的主节点上写入数据。严重的话，脑裂会进一步导致数据丢失。也就是说，等到哨兵让原主库和新主库做全量同步后，原主库在切换期间保存的数据就丢失了。</p>
<p>详细点来说，当主从切换后，从库升级为新主库，原主库和新主库会重新进行数据的全量同步，在这个过程中会发生数据的丢失</p>
<img src="/wang-cheng/2022/05/20/%E9%9D%A2%E7%BB%8F%E4%BA%8C/%E8%84%91%E8%A3%82.png" class title="脑裂">
<ul>
<li>从服务器 Slave 向主服务器 Master 发送数据同步命令；</li>
<li>Master 主服务器接收到同步命令后，保存快照生成 RDB 文件，同时使用缓冲区记录从现在开始执行的所有写命令；</li>
<li><strong>Master 主服务器将快照文件（RDB文件）发送给 Slave 从服务器，Slave 从服务器接收到后，清空旧数据，载入新数据；</strong></li>
<li>Master 主服务器快照发送完后开始向 Slave 从服务器发送缓冲区的写命令，Slave 从服务器接收命令并执行，完成复制初始化；</li>
<li>此后 Master 主服务器每次执行一个写命令都会同步发送给 Slave 从服务器，保持相互之间数据的一致性；</li>
</ul>
<p>上述步骤中的关键一步就是，原主库需要清空本地的数据后加载新主库发送的 RDB 文件，这样一来，原主库在主从切换期间保存的新写数据就丢失了。</p>
<h2 id="spring-特性"><a href="#spring-特性" class="headerlink" title="spring 特性"></a><strong>spring 特性</strong></h2><p>三大特性：AOP、IOC、DI</p>
<h2 id="jdbc连接数据库的流程-原始jdbc使用流程"><a href="#jdbc连接数据库的流程-原始jdbc使用流程" class="headerlink" title="jdbc连接数据库的流程/原始jdbc使用流程"></a><strong>jdbc连接数据库的流程/原始jdbc使用流程</strong></h2><p>1、加载jdbc驱动程序；</p>
<p>2、创建数据库的连接；</p>
<p>3、创建preparedStatement；</p>
<p>4、执行SQL语句；</p>
<p>5、遍历结果集；</p>
<p>6、处理异常，关闭JDBC对象资源。</p>
<h2 id="throw和throws的区别"><a href="#throw和throws的区别" class="headerlink" title="throw和throws的区别"></a><strong>throw和throws的区别</strong></h2><ol>
<li><p>位置不同。throws用在函数上，后边跟的是异常类，可以跟多个异常类。throw用在函数内，后面跟的是异常对象。</p>
</li>
<li><p>throws用来声明异常，让调用者只知道该功能可能出现的问题，可以给出预先得处理方式。throw抛出具体的问题对象，执行到throw。功能就已经结束了跳转到调用者，并将具体的问题对象抛给调用者，也就是说throw语句独立存在时，下面不要定义其他语句，因为执行不到。</p>
</li>
</ol>
<p>3.throws表示出现异常的一种可能性，并不一定会发生这些异常，throw则是抛出了异常，执行throw则一定抛出了某种异常对象。</p>
<h2 id="终止for循环的方式"><a href="#终止for循环的方式" class="headerlink" title="终止for循环的方式"></a><strong>终止for循环的方式</strong></h2><ol>
<li><p>continue</p>
</li>
<li><p>break</p>
</li>
<li><p>return</p>
</li>
</ol>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/" class="post-title-link" itemprop="url">面经一</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-16 17:10:30" itemprop="dateCreated datePublished" datetime="2022-05-16T17:10:30+08:00">2022-05-16</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-19 09:43:10" itemprop="dateModified" datetime="2022-05-19T09:43:10+08:00">2022-05-19</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="开平方根-精确到两位小数"><a href="#开平方根-精确到两位小数" class="headerlink" title="开平方根(精确到两位小数)"></a><strong>开平方根(精确到两位小数)</strong></h2><p>暂无…</p>
<h2 id="实现大数的求和"><a href="#实现大数的求和" class="headerlink" title="实现大数的求和"></a><strong>实现大数的求和</strong></h2><h2 id="实现线程安全的单例模式"><a href="#实现线程安全的单例模式" class="headerlink" title="实现线程安全的单例模式"></a><strong>实现线程安全的单例模式</strong></h2><h2 id="队列和栈的应用场景"><a href="#队列和栈的应用场景" class="headerlink" title="队列和栈的应用场景"></a><strong>队列和栈的应用场景</strong></h2><p>栈：限定仅在表尾进行插入和删除操作的线性表</p>
<p>表尾这一端被称为栈顶，相反地另一端被称为栈底，向栈顶插入元素被称为进栈、入栈、压<br>栈，从栈顶删除元素又称作出栈</p>
<p>队列：队列是一种特殊的线性表，特殊之处在于它只允许在表的前端（front）进行删除操作，而在表的后端（rear）进行插入操作</p>
<p>进行插入操作的端称为队尾，进行删除操作的端称为队头，当队列中没有元素时，称为空队列</p>
<p>应用场景：</p>
<ol>
<li><p>栈</p>
<ul>
<li>借助栈的先进后出的特性，可以简单实现一个逆序数处的功能，首先把所有元素依次入栈，然后把所有元素出栈并输出</li>
<li>函数调用和递归的时候，每调用一个函数，底层都会进行入栈操作，出栈则返回函数的返回值</li>
</ul>
</li>
<li><p>队列</p>
<ul>
<li>队列的使用广泛应用在广度优先搜索中，例如层次遍历一个二叉树的节点值</li>
</ul>
</li>
</ol>
<h2 id="重载和重写介绍一下"><a href="#重载和重写介绍一下" class="headerlink" title="重载和重写介绍一下"></a><strong>重载和重写介绍一下</strong></h2><p>重载:发生在同一个类中，方法名必须相同，参数类型不同、个数不同、顺序不同，方法返回值和访问修饰符可以不同，发生在编译时。</p>
<p>重写:发生在子父类，方法名、参数列表必须相同，返回值范围小于等于父类，抛出的异常范围小于等于父类，访问修饰符范围大于等于父类；如果父类方法访问修饰符为private，则子类就不能重写该方法</p>
<h2 id="mysql锁介绍"><a href="#mysql锁介绍" class="headerlink" title="mysql锁介绍"></a><strong>mysql锁介绍</strong></h2><p>不同的存储引擎支持不同的锁机制</p>
<ol>
<li><p>MyISAM和MEMORY存储引擎采用的是表级锁</p>
</li>
<li><p>InnoDB存储引擎既支持行级锁（row-level locking），也支持表级锁，但默认情况下是采用行级锁。</p>
</li>
</ol>
<img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/MySQL%E9%94%81%E6%9C%BA%E5%88%B6.png" class title="MySQL锁机制">
<p>表级锁：开销小，加锁快；不会出现死锁；锁定粒度大，发生锁冲突的概率最高,并发度最低。</p>
<ul>
<li><p>对MyISAM表的读操作，不会阻塞其他用户对同一表的读请求，但会阻塞对同一表的写请求；</p>
</li>
<li><p>对 MyISAM表的写操作，则会阻塞其他用户对同一表的读和写操作；</p>
</li>
<li><p>MyISAM表的读操作与写操作之间，以及写操作之间是串行的（一个处理完才能处理下一个）</p>
</li>
</ul>
<p>行级锁：开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁冲突的概率最低,并发度也最高。</p>
<ul>
<li>行锁就是对数据库表中行记录的锁。比如事务 A，B 同时想要更新一行数据，在更新时一定会按照一定的顺序进行，而不能同时更新。行锁的目的就是减少像表级别的锁冲突，来提升业务的并发度。</li>
</ul>
<p>页锁：开销和加锁时间界于表锁和行锁之间；会出现死锁；锁定粒度界于表锁和行锁之间，并发度一般。</p>
<h2 id="mysql的乐观锁和悲观锁"><a href="#mysql的乐观锁和悲观锁" class="headerlink" title="mysql的乐观锁和悲观锁"></a><strong>mysql的乐观锁和悲观锁</strong></h2><p>悲观锁：顾名思义，就是很悲观，每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制，比如行锁，表锁等，读锁，写锁等，都是在做操作之前先上锁。</p>
<p>乐观锁：顾名思义，就是很乐观，每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据，可以使用版本号等机制。</p>
<h2 id="mysql索引类型"><a href="#mysql索引类型" class="headerlink" title="mysql索引类型"></a><strong>mysql索引类型</strong></h2><p>1、普通索引：是最基本的索引，它没有任何限制</p>
<p>2、唯一索引：索引列的值必须唯一，但允许有空值。</p>
<p>3、主键索引：是一种特殊的唯一索引，一个表只能有一个主键，不允许有空值。一般是在建表的时候同时创建主键索引</p>
<p>4、组合索引：指多个字段上创建的索引，只有在查询条件中使用了创建索引时的第一个字段，索引才会被使用。使用组合索引时遵循最左前缀集合</p>
<p>5、全文索引：主要用来查找文本中的关键字，而不是直接与索引中的值相比较</p>
<h2 id="mysql隔离级别"><a href="#mysql隔离级别" class="headerlink" title="mysql隔离级别"></a><strong>mysql隔离级别</strong></h2><p>读未提交</p>
<p>读已提交</p>
<p>可重复读</p>
<p>序列化</p>
<h2 id="介绍一下innodb和myasiam区别"><a href="#介绍一下innodb和myasiam区别" class="headerlink" title="介绍一下innodb和myasiam区别"></a><strong>介绍一下innodb和myasiam区别</strong></h2><ol>
<li><p>InnoDB支持事务，MyISAM不支持，对于InnoDB每一条SQL语言都默认封装成事务，自动提交，这样会影响速度，所以最好把多条SQL语言放在begin和commit之间，组成一个事务； </p>
</li>
<li><p>InnoDB支持外键，而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败； </p>
</li>
<li><p>InnoDB是聚集索引，使用B+Tree作为索引结构，数据文件是和（主键）索引绑在一起的（表数据文件本身就是按B+Tree组织的一个索引结构），必须要有主键，通过主键索引效率很高。但是辅助索引需要两次查询，先查询到主键，然后再通过主键查询到数据。因此，主键不应该过大，因为主键太大，其他索引也都会很大。</p>
</li>
<li><p>InnoDB不保存表的具体行数，执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数，执行上述语句时只需要读出该变量即可，速度很快（注意不能加有任何WHERE条件）；</p>
</li>
<li><p>Innodb不支持全文索引，而MyISAM支持全文索引，在涉及全文索引领域的查询效率上MyISAM速度更快高；PS：5.7以后的InnoDB支持全文索引了</p>
</li>
<li><p>MyISAM表格可以被压缩后进行查询操作</p>
</li>
<li><p>InnoDB支持表、行(默认)级锁，而MyISAM支持表级锁</p>
</li>
<li><p>InnoDB表必须有唯一索引（如主键）（用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键），而Myisam可以没有</p>
</li>
<li><p>Innodb存储文件有frm、ibd，而Myisam是frm、MYD、MYI</p>
<ul>
<li>Innodb：frm是表定义文件，ibd是数据文件</li>
<li>Myisam：frm是表定义文件，myd是数据文件，myi是索引文件</li>
</ul>
</li>
</ol>
<h2 id="Redis的K和V最大能存储多大内存"><a href="#Redis的K和V最大能存储多大内存" class="headerlink" title="Redis的K和V最大能存储多大内存"></a><strong>Redis的K和V最大能存储多大内存</strong></h2><ul>
<li><p>虽然Key的大小上限为512M，但是一般建议key的大小不要超过1KB,这样既可以节约存储空间，又有利于Redis进行检索</p>
</li>
<li><p>value的最大值也是512M.对于String类型的value值上限为512M，而集合、链表、哈希等key类型，单个元素的value上限也为512M。</p>
</li>
</ul>
<h2 id="防止缓存击穿"><a href="#防止缓存击穿" class="headerlink" title="防止缓存击穿"></a><strong>防止缓存击穿</strong></h2><ul>
<li><p>设置热点数据永远不过期</p>
</li>
<li><p>加互斥锁</p>
</li>
</ul>
<h2 id="CMS过程、CMS的哪个过程是STW-初始化阶段STW吗？为什么"><a href="#CMS过程、CMS的哪个过程是STW-初始化阶段STW吗？为什么" class="headerlink" title="CMS过程、CMS的哪个过程是STW,初始化阶段STW吗？为什么"></a><strong>CMS过程、CMS的哪个过程是STW,初始化阶段STW吗？为什么</strong></h2><p><code>STW(Stop-The-World)</code>: 是在垃圾回收算法执⾏过程当中，将JVM内存冻结、应用程序停顿的⼀种状态。</p>
<ol>
<li><p>初始标记（STW）:这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象，速度比较快，不用担心会停顿太长时间。</p>
</li>
<li><p>并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程，这个过程耗时较长但是不需要停顿用户线程，可以与垃圾收集线程一起并发运行。</p>
</li>
<li><p>重新标记（STW）:由于并发标记阶段可能某些用户线程会导致标记产生变动，因此这里需要再次暂停所有线程进行并发标记，这个时间会比初始标记时间长一点。</p>
</li>
<li><p>并发清除:最后就可以直接将所有标记好的无用对象进行删除，因为这些对象程序中也用不到了，所以可以与用户线程并发运行。</p>
</li>
</ol>
<p><strong>初始标记为什么需要STW？</strong></p>
<p>因为初始标记标记的是GC Root，而GC Root容易变动，比如栈帧中的本地变量表。所以需要STW。</p>
<p><strong>重新标记为什么需要STW？</strong></p>
<p>因为在重新标记之前是并发标记，在并发标记的期间会出现漏标和多标的对象，所以为了修正这部分对象，需要在重新标记期间STW。</p>
<h2 id="HashMap为什么不安全"><a href="#HashMap为什么不安全" class="headerlink" title="HashMap为什么不安全"></a><strong>HashMap为什么不安全</strong></h2><ol>
<li><p>put的时候导致的多线程数据不一致：</p>
<p> 这个问题比较好想象，比如有两个线程A和B，首先A希望插入一个key-value对到HashMap中，首先计算记录所要落到的桶的索引坐标，然后获取到该桶里面的链表头结点，此时线程A的时间片用完了，而此时线程B被调度得以执行，和线程A一样执行，只不过线程B成功将记录插到了桶里面，假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的，那么当线程B成功插入之后，线程A再次被调度运行时，它依然持有过期的链表头但是它对此一无所知，以至于它认为它应该这样做，如此一来就覆盖了线程B插入的记录，这样线程B插入的记录就凭空消失了，造成了数据不一致的行为。</p>
</li>
<li><p>扩容的时候导致的死循环</p>
</li>
</ol>
<h2 id="TreeSet和HashSet介绍一下"><a href="#TreeSet和HashSet介绍一下" class="headerlink" title="TreeSet和HashSet介绍一下"></a><strong>TreeSet和HashSet介绍一下</strong></h2><ul>
<li><p>TreeSet：红黑树结构，每一个元素都是树中的一个节点，插入的元素都会进行排序；</p>
<ul>
<li>对插入的元素进行排序，是一个有序的集合（主要与HashSet的区别）</li>
<li>底层使用红黑树结构，而不是哈希表结构</li>
<li>允许插入Null值</li>
<li>不允许插入重复元素</li>
<li>线程不安全</li>
</ul>
</li>
<li><p>HashSet：哈希表结构，主要利用HashMap的key来存储元素，计算插入元素的hashCode来获取元素在集合中的位置</p>
<ul>
<li>不允许出现重复因素</li>
<li>允许插入Null值</li>
<li>元素无序（添加顺序和遍历顺序不一致）</li>
<li>线程不安全，若2个线程同时操作HashSet，必须通过代码实现同步</li>
</ul>
</li>
</ul>
<h2 id="runnable和callable介绍一下"><a href="#runnable和callable介绍一下" class="headerlink" title="runnable和callable介绍一下"></a><strong>runnable和callable介绍一下</strong></h2><ol>
<li><p>Runnable只执行逻辑，不返回结果；Callable执行逻辑之后，会返回结果。</p>
</li>
<li><p>Runnable不支持抛出受检异常，异常需要自己在run()方法的实现中消化；Callable可以抛出异常。（方法定义上肉眼可见的差别）</p>
</li>
<li><p>Runnable的实现类可以通过构造方法直接传递给一个Thread，并执行；而Callable只能借助Future去执行并获取返回结果。</p>
</li>
</ol>
<h2 id="线程池拒绝策略"><a href="#线程池拒绝策略" class="headerlink" title="线程池拒绝策略"></a><strong>线程池拒绝策略</strong></h2><p>AbortPolicy:丢弃任务，并抛出拒绝执行的异常信息RejectedExecutionException</p>
<p>DiscardPolicy：直接丢弃</p>
<p>DiscardOldestPolicy：丢弃阻塞队列中最老的任务，将新任务加入</p>
<p>CallerRunsPolicy：当有新任务提交后，如果线程池没被关闭且没有能力执行，则把这个任务交于提交任务的线程执行，也就是谁提交任务，谁就负责执行任务</p>
<h2 id="什么是线程安全，为什么不安全"><a href="#什么是线程安全，为什么不安全" class="headerlink" title="什么是线程安全，为什么不安全"></a><strong>什么是线程安全，为什么不安全</strong></h2><p>当多个线程访问一个对象时，如果不用考虑这些线程在运行时环境下的调度和交替执行，并且不需要额外的同步，或者在调用方代码不做其他的协调操作，这个对象的行为获取的结果仍然是正确的，那个称这个对象是线程安全的，否则就是不安全</p>
<p>为什么不安全呢？</p>
<ol>
<li><p>CPU抢占执行</p>
</li>
<li><p>非原子性</p>
</li>
<li><p>编译器优化（指令重排） 编译器优化在单线程下执行没问题，多线程下优化会发生混乱</p>
</li>
<li><p>内存的不可见性</p>
</li>
<li><p>多个线程修改了同一个变量</p>
</li>
</ol>
<h2 id="一般怎么样才能做到线程安全"><a href="#一般怎么样才能做到线程安全" class="headerlink" title="一般怎么样才能做到线程安全"></a><strong>一般怎么样才能做到线程安全</strong></h2><p>原子性、可见性、有序性</p>
<h2 id="volatile原理、功能"><a href="#volatile原理、功能" class="headerlink" title="volatile原理、功能"></a><strong>volatile原理、功能</strong></h2><p>功能：</p>
<ol>
<li>保证变量的可见性<ul>
<li>当一个被volatile关键字修饰的变量被一个线程修改的时候，其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量写入数据的时候，虚拟机会强制它被值刷新到主内存中。当一个线程用到被volatile关键字修饰的值的时候，虚拟机会强制要求它从主内存中读取。</li>
</ul>
</li>
<li>屏蔽指令重排序<ul>
<li>指令重排序是编译器和处理器为了高效对程序进行优化的手段，它只能保证程序执行的结果时正确的，但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题，但是在多线程中就会出现问题。非常经典的例子是在单例方法中同时对字段加入voliate，就是为了防止指令重排序。</li>
</ul>
</li>
</ol>
<p><strong>可见性</strong></p>
<p>简单来说：当一个共享变量被volatile修饰时，它会保证修改的值会立即被更新到主存，当有其他线程需要读取时，它会去内存中读取新值。</p>
<p><strong>禁止重排序</strong></p>
<p>volatile禁止重排序是利用内存屏障，保证有序性。</p>
<p>(1) volatile会在变量写操作的前后加入两个内存屏障，来保证前面的写指令和后面的读指令是有序的。</p>
<img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/%E5%86%99%E6%93%8D%E4%BD%9C%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C.png" class title="写操作内存屏障 } (2) volatile在变量的读操作后面插入两个指令，禁止后面的读指令和写指令重排序。 {% asset_img 读操作内存屏障.png 读操作内存屏障">
<h2 id="synchronized和lock的区别"><a href="#synchronized和lock的区别" class="headerlink" title="synchronized和lock的区别"></a><strong>synchronized和lock的区别</strong></h2><img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/synchronized%E5%92%8Clock%E5%8C%BA%E5%88%AB.png" class title="synchronized和lock区别">
<ol>
<li><p>来源：lock是一个接口，而synchronized是java的一个关键字，synchronized是内置的语言实现；</p>
</li>
<li><p>异常是否释放锁：synchronized在发生异常时候会自动释放占有的锁，因此不会出现死锁；而lock发生异常时候，不会主动释放占有的锁，必须手动unlock来释放锁，可能引起死锁的发生。（所以最好将同步代码块用try catch包起来，finally中写入unlock，避免死锁的发生。）</p>
</li>
<li><p>是否响应中断：lock等待锁过程中可以用interrupt来中断等待，而synchronized只能等待锁的释放，不能响应中断；</p>
</li>
<li><p>是否知道获取锁：Lock可以通过trylock来知道有没有获取锁，而synchronized不能；</p>
</li>
<li><p>Lock可以提高多个线程进行读操作的效率。（可以通过readwritelock实现读写分离）</p>
</li>
<li><p>在性能上来说，如果竞争资源不激烈，两者的性能是差不多的，而当竞争资源非常激烈时（即有大量线程同时竞争），此时Lock的性能要远远优于synchronized。所以说，在具体使用时要根据适当情况选择。</p>
</li>
<li><p>synchronized使用Object对象本身的wait 、notify、notifyAll调度机制，而Lock可以使用Condition进行线程之间的调度</p>
</li>
</ol>
<p>synchronized原始采用的是CPU悲观锁机制，即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换，当有很多线程竞争锁的时候，会引起CPU频繁的上下文切换导致效率很低。</p>
<p>而Lock用的是乐观锁方式。所谓乐观锁就是，每次不加锁而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止。乐观锁实现的机制就是CAS操作</p>
<h2 id="synchronized什么时候给类加锁"><a href="#synchronized什么时候给类加锁" class="headerlink" title="synchronized什么时候给类加锁"></a><strong>synchronized什么时候给类加锁</strong></h2><p>类锁是锁住整个类的，当有多个线程来声明这个类的对象的时候将会被阻塞，直到拥有这个类锁的对象被销毁或者主动释放了类锁。这个时候在被阻塞住的线程被挑选出一个占有该类锁，声明该类的对象。其他线程继续被阻塞住。父类加了类锁，子类是不会收到影响的，相反也是如此。</p>
<h2 id="输入一个URL，到最终页面呈现经历了哪些过程"><a href="#输入一个URL，到最终页面呈现经历了哪些过程" class="headerlink" title="输入一个URL，到最终页面呈现经历了哪些过程"></a><strong>输入一个URL，到最终页面呈现经历了哪些过程</strong></h2><ol>
<li><p>DNS(域名系统)解析(如果IP直接访问则此过程省略)</p>
<ul>
<li>DNS是一个由分层的DNS服务器实现的分布式数据库，</li>
<li>DNS是一个使得主机能够查询分布式数据库的应用层协议</li>
<li>DNS运行在UDP上，使用53端口</li>
<li>先查浏览器缓存，再查host文件，再查DNS服务器</li>
</ul>
</li>
<li><p>构造HTTP协议的数据报</p>
<ul>
<li>从浏览器中输入，那请求方式是GET，加上报文头。然后将数据报交给运输层。</li>
</ul>
</li>
<li><p>客户端与服务器端进行TCP三次握手连接</p>
<img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.png" class title="三次握手">
</li>
<li><p>TCP把数据构造成一个TCP数据报</p>
<ul>
<li>在此之前已经完成了三次握手，那么这一步在TCP构造完后交给IP协议</li>
</ul>
</li>
<li><p>IP 协议构造成一个 IP 数据报</p>
<ul>
<li>此处要根据数据报的长度，以及MTU 的限制，考虑是否分包，如果需要分包，根据Ip报文中的16位标识符（标识是否是同一个包），13位偏移位（标识先后顺序），3位标志位（表示结束标记位）进行分包。包分完之后，需要查路由表，决定从哪个端口转发，完成上述步骤后，将数据交给数据链路层。</li>
</ul>
</li>
<li><p>构造一个以太网数据帧</p>
<ul>
<li>源mac，就是本机mac。</li>
<li>目的mac，根据上面 IP 确定的转发端口来决定的。</li>
</ul>
</li>
<li><p>物理层</p>
<ul>
<li>转换成光信号或者电信号，进行传输。</li>
</ul>
</li>
<li><p>传输到下一台路由器上</p>
<ul>
<li>路由器进行解析和分用（解析到传输层）</li>
<li>路由选择，继续查路由表</li>
<li>把数据再次封装，往下一个路由器设备上转发</li>
</ul>
</li>
<li><p>中间会经历若干路由器转发的过程…</p>
<ul>
<li>最终到达目标服务器，服务器又会进行分用。</li>
</ul>
</li>
<li><p>物理层把光电信号转换成二进制bit流，交给数据链路层</p>
</li>
<li><p>数据链路层解析出其中的 IP 数据报，交给网络层</p>
</li>
<li><p>网络层可能需要组包，解析出其中的传输层数据，交给传输层</p>
</li>
<li><p>TCP解析出其中的应用层数据，交给应用程序</p>
</li>
<li><p>服务器的应用程序，读取数据，根据请求计算响应，得到一个html页面</p>
</li>
<li><p>把页面构造成一个http响应报文，经过一系列的封装，分用，传输，最终回到客户端</p>
</li>
<li><p>浏览器解析渲染页面</p>
</li>
<li><p>连接结束</p>
</li>
</ol>
<h2 id="cookie和session区别"><a href="#cookie和session区别" class="headerlink" title="cookie和session区别"></a><strong>cookie和session区别</strong></h2><ul>
<li><p>安全性：Session 比 Cookie 安全，Session 是存储在服务器端的，Cookie 是存储在客户端的。</p>
</li>
<li><p>存取值的类型不同：Cookie 只支持存字符串数据，想要设置其他类型的数据，需要将其转换成字符串，Session 可以存任意数据类型。</p>
</li>
<li><p>有效期不同： Cookie 可设置为长时间保持，比如我们经常使用的默认登录功能，Session 一般失效时间较短，客户端关闭（默认情况下）或者 Session 超时都会失效。</p>
</li>
<li><p>存储大小不同： 单个 Cookie 保存的数据不能超过 4K，Session 可存储数据远高于 Cookie，但是当访问量过多，会占用过多的服务器资源。</p>
</li>
</ul>
<h2 id="TCP和UDP的区别"><a href="#TCP和UDP的区别" class="headerlink" title="TCP和UDP的区别"></a><strong>TCP和UDP的区别</strong></h2><ol>
<li><p>TCP面向连接（如打电话要先拨号建立连接）;UDP是无连接的，即发送数据之前不需要建立连接</p>
</li>
<li><p>TCP提供可靠的服务。也就是说，通过TCP连接传送的数据，无差错，不丢失，不重复，且按序到达;UDP尽最大努力交付，即不保 证可靠交付</p>
</li>
<li><p>TCP面向字节流，实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的<br>UDP没有拥塞控制，因此网络出现拥塞不会使源主机的发送速率降低（对实时应用很有用，如IP电话，实时视频会议等）</p>
</li>
<li><p>每一条TCP连接只能是点到点的;UDP支持一对一，一对多，多对一和多对多的交互通信</p>
</li>
<li><p>TCP首部开销20字节;UDP的首部开销小，只有8个字节</p>
</li>
<li><p>TCP的逻辑通信信道是全双工的可靠信道，UDP则是不可靠信道</p>
</li>
</ol>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/u012957549/article/details/105108120">https://blog.csdn.net/u012957549/article/details/105108120</a></p>
<h2 id="TCP三次握手和TCP四次挥手"><a href="#TCP三次握手和TCP四次挥手" class="headerlink" title="TCP三次握手和TCP四次挥手"></a><strong>TCP三次握手和TCP四次挥手</strong></h2><img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/TCP%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%92%8C%E5%9B%9B%E6%AC%A1%E5%88%86%E6%89%8B.png" class title="TCP三次握手和四次分手">
<p><strong>三次握手</strong></p>
<ol>
<li><p>第一次握手：建立连接。客户端发送连接请求报文段，将SYN位置为1，Sequence Number为x;然后，客户端进入SYN_SEND状态，等待服务器的确认;</p>
</li>
<li><p>第二次握手：服务器收到SYN报文段。服务器收到客户端的SYN报文段，需要对这个SYN报文段进行确认，设置Acknowledgment Number为x+1(Sequence Number+1);同时，自己自己还要发送SYN请求信息，将SYN位置为1，Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中，一并发送给客户端，此时服务器进入SYN_RECV状态;</p>
</li>
<li><p>第三次握手：客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1，向服务器发送ACK报文段，这个报文段发送完毕以后，客户端和服务器端都进入ESTABLISHED状态，完成TCP三次握手。</p>
</li>
</ol>
<p>完成了三次握手，客户端和服务器端就可以开始传送数据</p>
<p><strong>四次挥手</strong></p>
<ol>
<li><p>主机1(可以使客户端，也可以是服务器端)，设置Sequence Number和Acknowledgment Number，向主机2发送一个FIN报文段;此时，主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;</p>
</li>
<li><p>主机2收到了主机1发送的FIN报文段，向主机1回一个ACK报文段，Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2进入CLOSE_WAIT状态;</p>
</li>
<li><p>主机2向主机1发送FIN报文段，表示我也没有数据要发送了，请求关闭连接，同时主机2进入LAST_ACK状态;</p>
</li>
<li><p>主机1收到主机2发送的FIN报文段，向主机2发送ACK报文段，然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后，就关闭连接;此时，主机1等待2MSL后依然没有收到回复，则证明Server端已正常关闭，那好，主机1也可以关闭连接了。</p>
</li>
</ol>
<h2 id="TCP为什么要三次握手"><a href="#TCP为什么要三次握手" class="headerlink" title="TCP为什么要三次握手"></a><strong>TCP为什么要三次握手</strong></h2><p>为了防止已失效的连接请求报文段突然又传送到了服务端，因而产生错误。</p>
<p><strong>举例：</strong></p>
<p>“已失效的连接请求报文段”的产生在这样一种情况下：client发出的第一个连接请求报文段并没有丢失，而是在某个网络结点长时间的滞留了，以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后，就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段，同意建立连接。假设不采用“三次握手”，那么只要server发出确认，新的连接就建立了。由于现在client并没有发出建立连接的请求，因此不会理睬server的确认，也不会向server发送数据。但server却以为新的运输连接已经建立，并一直等待client发来数据。这样，server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况，client不会向server的确认发出确认。server由于收不到确认，就知道client并没有要求建立连接。”</p>
<h2 id="TCP为什么要四次挥手"><a href="#TCP为什么要四次挥手" class="headerlink" title="TCP为什么要四次挥手"></a><strong>TCP为什么要四次挥手</strong></h2><p>那四次分手又是为何呢?TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工 模式，这就意味着，当主机1发出FIN报文段时，只是表示主机1已经没有数据要发送了，主机1告诉主机2， 它的数据已经全部发送完毕了;但是，这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文 段时，表示它已经知道主机1没有数据发送了，但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN 报文段时，这个时候就表示主机2也没有数据要发送了，就会告诉主机1，我也没有数据要发送了，之后彼此 就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理，就需要了解四次分手过程中的状态变化。</p>
<ul>
<li><p>FIN_WAIT_1: 这个状态要好好解释一下，其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等 待对方的FIN报文。而这两种状态的区别是：FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时， 它想主动关闭连接，向对方发送了FIN报文，此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报 文后，则进入到FIN_WAIT_2状态，当然在实际的正常情况下，无论对方何种情况下，都应该马上回应ACK 报文，所以FIN_WAIT_1状态一般是比较难见到的，而FIN_WAIT_2状态还有时常常可以用netstat看到。 (主动方)</p>
</li>
<li><p>FIN_WAIT_2：上面已经详细解释了这种状态，实际上FIN_WAIT_2状态下的SOCKET，表示半连接，也即 有一方要求close连接，但另外还告诉对方，我暂时还有点数据需要传送给你(ACK信息)，稍后再关闭连接。 (主动方)</p>
</li>
<li><p>CLOSE_WAIT：这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN 报文给自己，你系统毫无疑问地会回应一个ACK报文给对方，此时则进入到CLOSE_WAIT状态。接下来呢，实 际上你真正需要考虑的事情是察看你是否还有数据发送给对方，如果没有的话，那么你也就可以 close这个 SOCKET，发送FIN报文给对方，也即关闭连接。所以你在CLOSE_WAIT状态下，需要完成的事情是等待你去关闭连接。(被动方)</p>
</li>
<li><p>LAST_ACK: 这个状态还是比较容易好理解的，它是被动关闭一方在发送FIN报文后，最后等待对方的ACK报 文。当收到ACK报文后，也即可以进入到CLOSED可用状态了。(被动方)</p>
</li>
<li><p>TIME_WAIT: 表示收到了对方的FIN报文，并发送出了ACK报文，就等2MSL后即可回到CLOSED可用状态了。 如果FINWAIT1状态下，收到了对方同时带FIN标志和ACK标志的报文时，可以直接进入到TIME_WAIT状态，而无 须经过FIN_WAIT_2状态。(主动方)</p>
</li>
<li><p>CLOSED: 表示连接中断。</p>
</li>
</ul>
<h2 id="http请求码常见的说一下，401和403说一下"><a href="#http请求码常见的说一下，401和403说一下" class="headerlink" title="http请求码常见的说一下，401和403说一下"></a><strong>http请求码常见的说一下，401和403说一下</strong></h2><ol>
<li><p>1xx（临时响应）表示临时响应并需要请求者继续执行操作的状态代码</p>
<ul>
<li>100（继续）请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分，正在等待其余部分</li>
<li>101（切换协议）请求者已要求服务器切换协议，服务器已确认并准备切换</li>
</ul>
</li>
<li><p>2xx（成功）表示服务器成功处理了请求的状态代码</p>
<ul>
<li>200（成功）服务器已成功处理了请求。通常，这表示服务器提供了请求的网页.如果针对您的 robots.txt 文件显示此状态，则表示 Googlebot 已成功检索到该文件</li>
<li>201（已创建）请求成功并且服务器创建了新的资源</li>
<li>202（已接受）服务器已接受请求，但尚未处理</li>
<li>203（非授权信息）服务器已成功处理了请求，但返回的信息可能来自另一来源</li>
<li>204（无内容）服务器成功处理了请求，但没有返回任何内容</li>
<li>205（重置内容）服务器成功处理了请求，但没有返回任何内容。 与 204 响应不同，此响应要求请求者重置文档视图（例如，清除表单内容以输入新内容）</li>
<li>206（部分内容）服务器成功处理了部分 GET 请求</li>
</ul>
</li>
<li><p>3xx（重定向）要完成请求，需要进一步操作。 通常，这些状态代码用来重定向。</p>
</li>
<li><p>4xx（请求错误）这些状态代码表示请求可能出错，妨碍了服务器的处理</p>
<ul>
<li>400（错误请求）服务器不理解请求的语法</li>
<li>401（未授权）请求要求身份验证。 对于需要登录的网页，服务器可能返回此响应</li>
<li>403（禁止）服务器拒绝请求。 如果您看到 Googlebot 在尝试抓取您网站上的有效网页时收到此状态代码（可以在 Google 网站管理员工具诊 断下的网络抓取页面上看到此信息），可能是您的服务器或主机拒绝Googlebot 访问</li>
<li>404（未找到）服务器找不到请求的网页。 例如，如果请求服务器上不存在的网页，服务器通常会返回此代码。如果您的网站上没有 robots.txt 文件，而您在 Google 网站管理员工具”诊断”标签的robots.txt 页上看到此状态，那么这是正确的状态。 但是，如果您有 robots.txt 文件而又看到此状态，则说明您的 robots.txt 文件可能命名错误或位于错误的位置 （该文件应当位于顶级域名，名为robots.txt）。如果您看到有关 Googlebot 尝试抓取的网址的此状态（在”诊断”标签的 HTTP 错误页上），则表示 Googlebot 追踪的可能是另一个页面的无效链接（是旧链接或输入有误的链接）</li>
<li>405（禁用的方法）禁用请求中指定的方法</li>
<li>406（不可接受）无法使用请求的内容特性响应请求的网页</li>
<li>407（需要代理授权）此状态代码与 401（未授权）类似，但指定请求者应当授权使用代理。 如果服务器返回此响应，还会指明请求者应当使用的代理</li>
<li>408（请求超时）服务器等候请求时发生超时</li>
</ul>
</li>
</ol>
<h2 id="http请求方式-get-post…"><a href="#http请求方式-get-post…" class="headerlink" title="http请求方式(get/post…)"></a><strong>http请求方式(get/post…)</strong></h2><ol>
<li><p>GET请求指定的页面信息，并返回实体主体</p>
</li>
<li><p>POST向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中，post请求可能会导致新的资源的建立和/或已有资源的修改</p>
</li>
<li><p>Head类似于get请求，只不过返回的响应中没有具体的内容，用于获取报头</p>
</li>
<li><p>Options允许客户端查看服务器的性能</p>
</li>
<li><p>Put从客户端向服务器传送的数据取代指定的文档的内容</p>
</li>
<li><p>Delete请求服务器删除指定的页面</p>
</li>
<li><p>Trace回显服务器收到的请求，主要用于测试或诊断</p>
</li>
<li><p>PATCH对 PUT 方法的补充，用来对已知资源进行局部更新。</p>
</li>
<li><p>CONNECT能够将连接改为管道方式的代理服务器。</p>
</li>
</ol>
<h2 id="http和https的区别"><a href="#http和https的区别" class="headerlink" title="http和https的区别"></a><strong>http和https的区别</strong></h2><ol>
<li><p>Http和https使用完全不同的连接方法和不同的端口。前者是80，后者是443。Http连接是非常简单和无状态的；HttpS协议是由SSL/TLS+HTTP协议构建的网络协议，可用于加密传输和身份认证。它比HTTP协议更安全。</p>
</li>
<li><p>超文本传输协议，缩写为HTTP，是分布式、协作式和超媒体信息系统的应用层协议，是万维网数据通信的基础，也是互联网上使用最广泛的网络传输协议。HTTP最初被设计为提供一种发布和接收HTML页面的方式。</p>
</li>
<li><p>HTTPS协议之所以安全，是因为HTTPS协议对传输的数据进行加密，而加密过程是由非对称加密实现的。然而，HTTPS在内容传输的加密过程中使用对称加密，而非对称加密只在证书验证阶段发挥作用。</p>
</li>
</ol>
<h2 id="BIO和NIO的区别"><a href="#BIO和NIO的区别" class="headerlink" title="BIO和NIO的区别"></a><strong>BIO和NIO的区别</strong></h2><ul>
<li><p>BIO是传统的Java IO编程，其基本的类和接口在java.io包中</p>
</li>
<li><p>BIO(blocking I/O)：同步阻塞，服务器实现模式为一个连接一个线程，即客户端有连接请求时服务器端就需要启动一个线程进行处理，如果这个连接不做任何事情会造成不必要的线程开销</p>
</li>
<li><p>BIO方式使用于连接数目比较小且固定的架构，这种服务方式对服务器资源要求比价高，并且局限于应用中，JDK1.4以前的唯一选择，程序简单易理解</p>
</li>
</ul>
<p>基本模型：<br><img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/BIO.png" class title="BIO"></p>
<ul>
<li><p>NIO全称 java non-blocking IO。从JDK 1.4开始，java提供了一些列改进的输入/输出（I/O）的新特性，被称为NIO，是同步非阻塞的</p>
</li>
<li><p>NIO相关类都被放在java.nio包及其子包下</p>
</li>
<li><p>NIO三大核心部分：Channel（通道），Buffer（缓冲区），Selector（选择器）</p>
</li>
<li><p>NIO是面向缓冲区的，或者面向块编程的。数据读取到一个它稍后处理的缓冲区，需要时可在缓冲区内前后移动，这就增加了处理过程中的灵活性，使用它可以提供非阻塞的高伸缩性网络</p>
</li>
<li><p>Java NIO的非阻塞模式，使一个线程从某通道发送或者读取数据，但是它仅能得到目前可用的数据，如果目前没有可用的数据时，就什么都不会获取，而不是保持线程阻塞，所以直至数据变的可读取之前，该线程可以继续做其他事情。非阻塞就是如此，一个线程请求写入一些数据到某通道，但不需要等待它完全写入，这个线程同时可以去做别的事情</p>
</li>
<li><p>通俗来讲：NIO是可以做到用一个线程处理多个操作的。假设有10000个请求过来，根据实际情况，可以分配50或100个线程来处理。不想BIO一样需要分配10000个线程来处理</p>
</li>
</ul>
<p>基本模型：</p>
<img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/NIO.png" class title="NIO">
<p>其中Selector（选择器）的作用是循环监听多个客户端连接通道，如果通道中没有数据即客户端没有请求时它可以去处理别的通道或者做其他的事情，如果通道中有数据他就会选择这个通道然后进行处理，这就做到了一个线程处理多个连接。</p>
<p>区别：</p>
<ul>
<li><p>NIO以流的方式处理数据，NIO以块的方式处理数据，块IO的效率比流IO高很多。（比如说流IO他是一个流，你必须时刻去接着他，不然一些流就会丢失造成数据丢失，所以处理这个请求的线程就阻塞了他无法去处理别的请求，他必须时刻盯着这个请求防止数据丢失。而块IO就不一样了，线程可以等他的数据全部写入到缓冲区中形成一个数据块然后再去处理他，在这期间该线程可以去处理其他请求）</p>
</li>
<li><p>BIO是阻塞的，NIO是非阻塞的</p>
</li>
<li><p>BIO基于字节流和字符流进行操作的，而NIO基于Channel（通道）和Buffer（缓冲区）进行操作的，数据总是从通道读取到缓冲区中，或者从缓冲区写入到通道中。Selector（选择器）用于监听多个通道事件，因此使用单个线程就可以监听多个客户端通道</p>
</li>
</ul>
<h2 id="ArrayList的扩容"><a href="#ArrayList的扩容" class="headerlink" title="ArrayList的扩容"></a><strong>ArrayList的扩容</strong></h2><ul>
<li><p>ArrayList每次扩容都是原来的1.5倍</p>
</li>
<li><p>数组进行扩容时，会将老数组中的元素重新拷贝一份到新数组中，每次数组容量的增长大约是其原容量的1.5倍</p>
</li>
<li><p>代价是很高的，因此在实际使用时，我们应该尽量避免数组容量的扩张。尽可能，就指定其容量，以避免数组扩容的发生</p>
</li>
<li><p>创建方式不同，容量不同</p>
</li>
</ul>
<p>ArrayList扩容过程：</p>
<p>当使用无参构造时，并不会立即扩容，而是在第一次使用add()方法时才会扩容。</p>
<p>当调用add()方法添加元素，会在方法内部判断ArrayList是否已经达到最大容量。如果达到了最大容量，则调用grow()方法进行扩容。</p>
<p>在grow()方法中会进行是否是无参构造第一次扩容的判断，如果是，则默认扩容大小为10，如果不是，则调用newLength()方法计算应该扩容的大小，一般为1.5倍，然后调用Arrays.copyOf()方法，将原数组元素转移到扩容后的新数组当中，完成数据迁移</p>
<h2 id="网络的模型"><a href="#网络的模型" class="headerlink" title="网络的模型"></a><strong>网络的模型</strong></h2><img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/%E4%B8%83%E5%B1%82%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B.png" class title="七层网络模型">
<h2 id="事务的隔离性"><a href="#事务的隔离性" class="headerlink" title="事务的隔离性"></a><strong>事务的隔离性</strong></h2><p>隔离机制</p>
<ul>
<li><p>读未提交：一个事务还没有提交前，它做的变更能被别的事务看见</p>
</li>
<li><p>读已提交：一个事务提交后，其变更才能被其他事务看见</p>
</li>
<li><p>可重复读：一个事务执行过程中看见的数据，和这个事务在启动时一致（被其他事物修改也没用）。在此机制下，未提交变更对其他事务也是不可见的</p>
</li>
<li><p>串行化：对于数据操作，写会加写锁，读会加读锁。当出现读写锁冲突时，后访问的事务必须等前一个事务执行完成，才能继续执行。</p>
</li>
</ul>
<p>一般我们将Mysql的隔离级别设置为”读提交”</p>
<ol>
<li><p>“读未提交” 隔离级别下，直接返回记录上的最新值，没有视图概念。</p>
</li>
<li><p>“读提交” 隔离级别下，视图是在每个查询 SQL 语句开始执行时创建的。</p>
</li>
<li><p>“可重复读” 隔离级别下，视图是在事务启动时创建的，整个事务执行期间都用这个视图。</p>
</li>
<li><p>“串行化” 隔离级别下，直接用加锁的方式来避免并行访问，不创建视图。</p>
</li>
</ol>
<p>MVCC的内容等……</p>
<h2 id="内核态和用户态的区别"><a href="#内核态和用户态的区别" class="headerlink" title="内核态和用户态的区别"></a><strong>内核态和用户态的区别</strong></h2><h3 id="用户态和内核态"><a href="#用户态和内核态" class="headerlink" title="用户态和内核态"></a><strong>用户态和内核态</strong></h3><p>用户空间中的代码被限制了只能使用一个局部的内存空间，我们说这些程序在用户态（User Mode） 执行。内核空间中的代码可以访问所有内存，我们称这些程序在内核态（Kernal Mode） 执行。</p>
<p>如果用户态程序需要执行系统调用，就需要切换到内核态执行</p>
<img src="/wang-cheng/2022/05/16/%E9%9D%A2%E7%BB%8F%E4%B8%80/trap%E4%B8%AD%E6%96%AD.png" class title="trap中断">
<p>内核程序执行在内核态（Kernal Mode），用户程序执行在用户态（User Mode）。当发生系统调用时，用户态的程序发起系统调用。因为系统调用中牵扯特权指令，用户态程序权限不足，因此会中断执行，也就是 Trap（Trap 是一种中断）。</p>
<p>发生中断后，当前 CPU 执行的程序会中断，跳转到中断处理程序。内核程序开始执行，也就是开始处理系统调用。内核处理完成后，主动触发 Trap，这样会再次发生中断，切换回用户态工作。</p>
<h3 id="用户态切换到内核态的三种方式"><a href="#用户态切换到内核态的三种方式" class="headerlink" title="用户态切换到内核态的三种方式"></a><strong>用户态切换到内核态的三种方式</strong></h3><ol>
<li><p>系统调用：这是用户态进程主动要求切换到内核态的一种方式，用户态进程通过系统调用申请使 用操作系统提供的服务程序完成工作，比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户 特别开放的一个中断来实现。</p>
</li>
<li><p>异常：当CPU在执行运行在用户态下的程序时，发生了某些事先不可知的异常，这时会触发由当前运行进程切换到处理此异常的内核相关程序中，也就转到了内核态，比如缺页异常。</p>
</li>
<li><p>外围设备的中断：当外围设备完成用户请求的操作后，会向CPU发出相应的中断信号，这时CPU会 暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序，如果先前执行的指令是用户态下的程序，那么这个转换的过程自然也就发生了由用户态到 内核态的切换。比如硬盘读写操作完成，系统会切换到硬盘读写的中断处理程序中执行后续操作等。</p>
</li>
</ol>
<h2 id="DNS协议是怎么实现的，用到了什么协议"><a href="#DNS协议是怎么实现的，用到了什么协议" class="headerlink" title="DNS协议是怎么实现的，用到了什么协议"></a><strong>DNS协议是怎么实现的，用到了什么协议</strong></h2><p>DNS(域名解析)协议能够将域名映射成IP地址</p>
<p>域名服务器是指管理域名的主机和相应的软件，它可以管理所在分层的域的相关信息。一个域名服务器所负责管理的分层叫作 区 (ZONE)。域名的每层都设有一个域名服务器：</p>
<ul>
<li>根域名服务器</li>
<li>顶级域名服务器</li>
<li>权限域名服务器</li>
</ul>
<h3 id="DNS-查询方式"><a href="#DNS-查询方式" class="headerlink" title="DNS 查询方式"></a><strong>DNS 查询方式</strong></h3><ul>
<li><p>递归查询</p>
</li>
<li><p>迭代查询</p>
</li>
</ul>
<h3 id="完整域名解析过程"><a href="#完整域名解析过程" class="headerlink" title="完整域名解析过程"></a><strong>完整域名解析过程</strong></h3><ol>
<li><p>首先搜索浏览器的 DNS 缓存，缓存中维护一张域名与 IP 地址的对应表；</p>
</li>
<li><p>若没有命中，则继续搜索操作系统的 DNS 缓存；</p>
</li>
<li><p>若仍然没有命中，则操作系统将域名发送至本地域名服务器，本地域名服务器查询自己的 DNS 缓存，查找成功则返回结果（注意：主机和本地域名服务器之间的查询方式是递归查询）；</p>
</li>
<li><p>若本地域名服务器的 DNS 缓存没有命中，则本地域名服务器向上级域名服务器进行查询，通过以下方式进行迭代查询（注意：本地域名服务器和其他域名服务器之间的查询方式是迭代查询，防止根域名服务器压力过大）</p>
<ul>
<li><p>首先本地域名服务器向根域名服务器发起请求，根域名服务器是最高层次的，它并不会直接指明这个域名对应的 IP 地址，而是返回顶级域名服务器的地址，也就是说给本地域名服务器指明一条道路，让他去这里寻找答案。</p>
</li>
<li><p>本地域名服务器拿到这个顶级域名服务器的地址后，就向其发起请求，获取权限域名服务器的地址。</p>
</li>
<li><p>本地域名服务器根据权限域名服务器的地址向其发起请求，最终得到该域名对应的 IP 地址。</p>
</li>
</ul>
</li>
<li><p>本地域名服务器将得到的 IP 地址返回给操作系统，同时自己将 IP 地址缓存起来。</p>
</li>
<li><p>操作系统将 IP 地址返回给浏览器，同时自己也将 IP 地址缓存起来。</p>
</li>
<li><p>至此，浏览器就得到了域名对应的 IP 地址，并将 IP 地址缓存起来。</p>
</li>
</ol>
<h2 id="TCP怎么保证可靠性-相关的协议、算法"><a href="#TCP怎么保证可靠性-相关的协议、算法" class="headerlink" title="TCP怎么保证可靠性(相关的协议、算法)"></a><strong>TCP怎么保证可靠性(相关的协议、算法)</strong></h2><ul>
<li><p>首先，TCP 的连接是基于三次握手，而断开则是四次挥手。确保连接和断开的可靠性。</p>
</li>
<li><p>其次，TCP 的可靠性，还体现在有状态;TCP 通过校验和、ACK 应答、超时重传来记录哪些数据发送了，哪些数据被接受了，哪些没有被接受，并且保证数据包按序到达，保证数据传输不出差错。</p>
</li>
<li><p>再次，TCP 的可靠性，还体现在可控制。通过流量控制（滑动窗口）和拥塞控制来控制发送方发送速率。</p>
</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/u014401141/article/details/122961741">https://blog.csdn.net/u014401141/article/details/122961741</a></p>
<h2 id="String底层是怎么实现的"><a href="#String底层是怎么实现的" class="headerlink" title="String底层是怎么实现的"></a><strong>String底层是怎么实现的</strong></h2><p>底层最主要的实现是 final char[ ]; final修饰后当这个属性第一次被赋值后就不可以改变了，所以当我们拼接字符串的时候，每次拼接都会创建一个新的对象。所以不允许大量的拼接，否则会产生大量的对象，影响性能。</p>
<h2 id="OSI和ICP-IP的联系"><a href="#OSI和ICP-IP的联系" class="headerlink" title="OSI和ICP/IP的联系"></a><strong>OSI和ICP/IP的联系</strong></h2><p>osi模型是一个简称，全名为开放式系统互联通信参考模型，这是一种概念上的模型，是由国际标准化组织提出的一个概念。</p>
<p>tcp也是个简称，全名为传输控制协议，这是一种面向有连接的，十分可靠的，并且是基础字节流的传输层通信协议。</p>
<p>ip是国籍互联协议的缩写，是tcp/ip这个体系中的网络层协议。</p>
<p>相同点：</p>
<ul>
<li><p>两个协议都分层；</p>
</li>
<li><p>OSI参考模型的网络层与TPC/IP互联网层的功能几乎相同；</p>
</li>
<li><p>以传输层为界，上层都依赖传输层提供端到端的，与网络环境无关的传输服务。</p>
</li>
</ul>
<p>不同点：</p>
<ul>
<li><p>TCP/IP并未对网络接口层细分</p>
</li>
<li><p>OSI分层模型在前，协议规范灾后</p>
</li>
<li><p>OSI对服务和协议做了特别明显的区别，而TCP/IP并没有细分。</p>
</li>
<li><p>OSI为7层模型，TCP/IP为5层模型</p>
</li>
<li><p>TCP/IP模型的第三层仅支持IP协议，OSI支持所有的网络层协议</p>
</li>
</ul>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/" class="post-title-link" itemprop="url">synchronized等锁机制</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-05-05 00:27:41" itemprop="dateCreated datePublished" datetime="2022-05-05T00:27:41+08:00">2022-05-05</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-15 10:52:50" itemprop="dateModified" datetime="2022-05-15T10:52:50+08:00">2022-05-15</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="锁的本质"><a href="#锁的本质" class="headerlink" title="锁的本质"></a><strong>锁的本质</strong></h2><ul>
<li><p>用于保障数据的一致性</p>
</li>
<li><p>可以是原子性保障，也可以是使用补偿自旋锁</p>
</li>
</ul>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/lioncatch/article/details/105919666">https://blog.csdn.net/lioncatch/article/details/105919666</a></p>
<h2 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a><strong>偏向锁</strong></h2><p>偏向第一个拿到锁的线程。</p>
<p>即第一个拿到锁的线程，锁会在对象头 Mark Word 中通过 CAS 记录该线程 ID，该线程以后每次拿锁时都不需要进行 CAS（指轻量级锁）。</p>
<p>如果该线程正在执行同步代码块时有其他线程在竞争（指其他线程尝试 CAS 让 Mark Word 设置自己的线程 ID），会被升级为轻量级锁。</p>
<p>如果其他线程发现 Mark Word 里记的不是自己，且发现原持有偏向锁的线程已经执行完同步代码块，会尝试 CAS 把 Mark Word 中的改为自己的线程 ID。</p>
<h2 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a><strong>轻量级锁</strong></h2><p>轻量级锁就是通过 CAS 进行加锁的。</p>
<p>JVM 会给线程的栈帧中创建一个叫锁记录 Lock Record 的空间，把对象头 Mark Word 复制到该空间里（Displaced Mark Word），并通过CAS 尝试把原对象头 Mark Word 中锁记录指针指向该锁记录。如果成功，表示线程拿到了锁。如果失败，则进行自旋（自旋锁），自旋超过一定次数时升级为重量级锁，这时该线程会被内核挂起。</p>
<h2 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a><strong>自旋锁</strong></h2><p>轻量级锁膨胀为重量级锁前，线程在执行 monitorenter 指令进入等待队列时，会通过自旋去尝试获得锁。</p>
<p>如果自旋超过一定次数时还未拿到锁，就会进入阻塞状态，等待内核来调度。此时会发生内核态与用户态之间的上下文切换，所以会影响性能（引入自旋锁就是为了减少这个开销）。</p>
<p>因为后面的线程也先进行自旋尝试获取锁，所以这对于已被阻塞的那些线程来说，会不公平。</p>
<h2 id="重量级锁"><a href="#重量级锁" class="headerlink" title="重量级锁"></a><strong>重量级锁</strong></h2><p>重量级锁就是通过内核来操作线程。因为频繁出现内核态与用户态的切换，会严重影响性能。</p>
<p>升级为重量级锁时会在堆中创建 monitor 对象，并将 Mark Word 指向该 monitor 对象。monitor 中有 cxq（ContentionList），EntryList ，WaitSet，owner：</p>
<img src="/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81.jpg" class title="重量级锁">
<h2 id="锁升级的流程图"><a href="#锁升级的流程图" class="headerlink" title="锁升级的流程图"></a><strong>锁升级的流程图</strong></h2><img src="/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/%E9%94%81%E5%8D%87%E7%BA%A7%E6%B5%81%E7%A8%8B.png" class title="锁升级流程">
<p><strong>升级流程</strong>：</p>
<ol>
<li><p>当线程第一次访问synchronized锁的时候，synchronized会由无锁状态变为偏向锁，偏向当前线程</p>
<ul>
<li>synchoronized锁对象会在对象头中的mark word中记录下当前线程的ID，然后将自己升级为偏向锁。</li>
<li>这个过程是由JVM去实现的</li>
</ul>
</li>
<li><p>当线程第二次访问synchronized锁的时候，发现synchronized锁对象是偏向自己的，那么自己不需要重新获取锁对象就可以直接执行里面的方法</p>
<ul>
<li>这样做可以提高线程的效率，不需要再转换线程上下文</li>
</ul>
</li>
<li><p>当另一个线程到来，想要获取这个锁对象。这时候synchronized锁就会升级为轻量级锁。</p>
<ul>
<li>此时这个线程就会进入自旋状态，不停地请求锁对象(此时会有性能消耗，因为cpu需要不停地进行线程的上下文切换)，看正在执行的线程是否已经释放掉了锁(该过程是在JVM中进行的)</li>
</ul>
</li>
<li><p>当有很多线程都来请求锁对象或者线程自旋了很多次，这时候锁对象就会升级称为重量级锁</p>
<ul>
<li>此时就由OS来完成。OS会将自旋的线程设置为等待状态，放入等待队列，让cpu先处理当前线程，等当前线程处理完了，等待队列当中的线程才会依次执行。</li>
</ul>
</li>
</ol>
<h2 id="ReentrantLock和synchronized对比"><a href="#ReentrantLock和synchronized对比" class="headerlink" title="ReentrantLock和synchronized对比"></a><strong>ReentrantLock和synchronized对比</strong></h2><ol>
<li><p>底层实现的区别</p>
<ul>
<li>synchronized是JVM层面的锁，是JAVA关键字，ReetrantLock是API层面的锁</li>
</ul>
</li>
<li><p>是否可手动释放</p>
<ul>
<li>synchronized不需要手动释放，系统会自动让线程释放对锁的占用。ReetrantLock则需要手动释放</li>
</ul>
</li>
<li><p>是否可中断</p>
<ul>
<li><p>synchronized是不可中断类型的锁，除非加锁的代码中出现异常或正常执行完成</p>
</li>
<li><p>ReetrantLock则可以中断</p>
</li>
</ul>
</li>
<li><p>是否为公平锁</p>
<ul>
<li>synchronized是非公平锁，ReentrantLock则既可以公平锁又可以非公平锁</li>
</ul>
</li>
<li><p>锁是否可绑定Condition</p>
<ul>
<li>synchronized 不能绑定。</li>
<li>ReentrantLock可以绑定</li>
</ul>
</li>
<li><p>锁的对象</p>
<ul>
<li><p>synchronized锁的是对象，锁是保存在对象头里面，根据对象头数据来标识是否有线程获得锁/争抢锁</p>
</li>
<li><p>ReentrantLock锁的是线程，根据进入的线程和int类型的state标识锁的获得/争抢</p>
</li>
</ul>
</li>
</ol>
<h2 id="ReentrantLock-的原理"><a href="#ReentrantLock-的原理" class="headerlink" title="ReentrantLock 的原理"></a><strong>ReentrantLock 的原理</strong></h2><p>ReentrantLock底层是由AQS去实现的</p>
<h3 id="AQS"><a href="#AQS" class="headerlink" title="AQS"></a><strong>AQS</strong></h3><p>AQS可以理解为一个可以实现锁的框架</p>
<p><strong>公平锁</strong><br><img src="/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/AQS.png" class title="AQS"></p>
<ul>
<li><p>第一步：获取状态的state的值</p>
<ol>
<li>如果state=0即代表锁没有被其他线程占用，执行第二步</li>
<li>如果state!=0则代表锁正在被其他线程占用，执行第三步</li>
</ol>
</li>
<li><p>第二步：判断队列中是否有线程在排队等待</p>
<ol>
<li>如果不存在则直接将锁的所有者设置为当前线程，且更新状态state</li>
<li>如果存在就入队</li>
</ol>
</li>
<li><p>第三步：判断锁的所有者是不是当前线程</p>
<ol>
<li>如果是则更新状态 state 的值</li>
<li>如果不是，线程进入队列排队等待</li>
</ol>
</li>
</ul>
<p><strong>非公平锁</strong><br><img src="/wang-cheng/2022/05/05/synchronized%E7%AD%89%E9%94%81%E6%9C%BA%E5%88%B6/%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81.png" class title="非公平锁"></p>
<ul>
<li><p>第一步：获取状态的 state 的值</p>
<ol>
<li>如果 state=0 即代表锁没有被其它线程占用，则设置当前锁的持有者为当前线程，该操作用 CAS 完成。</li>
<li>如果不为 0 或者设置失败，代表锁被占用进行下一步</li>
</ol>
</li>
<li><p>此时获取 state 的值</p>
<ol>
<li>如果是，则给 state+1，获取锁</li>
<li>如果不是，则进入队列等待</li>
<li>如果是 0，代表刚好线程释放了锁，此时将锁的持有者设为自己</li>
<li>如果不是 0，则查看线程持有者是不是自己</li>
</ol>
</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a><strong>总结</strong></h2><p>ReentrantLock 和 Synchronized 的区别：</p>
<ul>
<li><p>其实 ReentrantLock 和 Synchronized 最核心的区别就在于 Synchronized 适合于并发竞争低的情况，因为 Synchronized 的锁升级如果最终升级为重量级锁在使用的过程中是没有办法消除的，意味着每次都要和 cpu 去请求锁资源，而 ReentrantLock 主要是提供了阻塞的能力，通过在高并发下线程的挂起，来减少竞争，提高并发能力。</p>
</li>
<li><p>Synchronized 是一个关键字，是由 JVM 层面去实现的，而 ReentrantLock 是由 java api 去实现的。</p>
</li>
<li><p>Synchronized 是隐式锁，可以自动释放锁，ReentrantLock 是显式锁，需要手动释放锁。</p>
</li>
<li><p>ReentrantLock 可以让等待锁的线程响应中断，而 Synchronized 却不行，使用 Synchronized 时，等待的线程会一直等待下去，不能够响应中断。</p>
</li>
<li><p>ReentrantLock 可以获取锁状态，而 Synchronized 不能。</p>
</li>
</ul>
<p><strong>动态高并发时为什么推荐 ReentrantLock 而不是 Synchronized？</strong></p>
<p>Synchronized 升级为重量级锁后无法在正常情况下完成降级，而 ReentrantLock 是通过阻塞来提高性能的，在设计模式上就体现出了对多线程情况的支持。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/29/ConcurrentHashMap%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/29/ConcurrentHashMap%E6%BA%90%E7%A0%81%E4%B8%93%E9%A2%98/" class="post-title-link" itemprop="url">ConcurrentHashMap源码专题</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-29 09:26:20" itemprop="dateCreated datePublished" datetime="2022-04-29T09:26:20+08:00">2022-04-29</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-01 12:38:07" itemprop="dateModified" datetime="2022-05-01T12:38:07+08:00">2022-05-01</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h1 id="ConcurrentHashMap源码分析"><a href="#ConcurrentHashMap源码分析" class="headerlink" title="ConcurrentHashMap源码分析"></a><strong>ConcurrentHashMap源码分析</strong></h1><p>ConcurrentHashMap在1.7的时候，new ConcurrentHashMap(32)的时候，此时数组长度就是32。但是在1.8之后，new ConcurrentHashMap(32)就不再是32，而是64。</p>
<p>因为ConcurrentHashMap和HashMap一样，是因为他要保证它的初始值是2的幂次方，这样可以使得添加的元素均匀分布在HashMap中的数组上，减少hash碰撞。</p>
<h2 id="ConcurrentHashMap初始化-CAS加自旋"><a href="#ConcurrentHashMap初始化-CAS加自旋" class="headerlink" title="ConcurrentHashMap初始化 CAS加自旋"></a><strong>ConcurrentHashMap初始化</strong> CAS加自旋</h2><p>ConcurrentHashMap的初始化操作(ConcurrentHashMap没用锁，提高了效率)</p>
<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">private final Node&lt;K,V&gt;[] initTable()&#123;</span><br><span class="line">    Node&lt;K,V&gt;[] <span class="keyword">tab</span>; int <span class="keyword">sc</span>;</span><br><span class="line">    <span class="keyword">while</span>((<span class="keyword">tab</span> = <span class="keyword">table</span>) == null || <span class="keyword">tab</span>.length == 0)&#123;</span><br><span class="line">        <span class="keyword">if</span>((<span class="keyword">sc</span>=sizeCtl) &lt; 0)&#123;</span><br><span class="line">            Thread.yield();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">U</span>.comparaeAndSwapInt(this,SIZECTL,<span class="keyword">sc</span>,-1))&#123;</span><br><span class="line">            try&#123;</span><br><span class="line">                <span class="keyword">if</span>((<span class="keyword">tab</span>=<span class="keyword">table</span>) == null || <span class="keyword">tab</span>.length==0)&#123;</span><br><span class="line">                    int <span class="keyword">n</span> = (<span class="keyword">sc</span>&gt;0)?<span class="keyword">sc</span>:DEFAULT_CAPACITY;</span><br><span class="line">                    @SuppressWarning(<span class="string">&quot;unchecked&quot;</span>)</span><br><span class="line">                    Node&lt;K,V&gt;[] nt = (Nde&lt;K,V&gt;[])new Node&lt;?,?&gt;[<span class="keyword">n</span>];</span><br><span class="line">                    <span class="keyword">table</span> = <span class="keyword">tab</span> = nt;</span><br><span class="line">                    <span class="keyword">sc</span> = <span class="keyword">n</span> - (<span class="keyword">n</span> &gt;&gt;&gt; 2);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;finally&#123;</span><br><span class="line">                sizeCtl = <span class="keyword">sc</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">tab</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>其中sizeCtl是成员变量，该变量为0时表示还未进行初始化，此时容器默认容量为16。当该变量为正数时表示为该容器设置了大小。当该变量为-1时，表示容器正在进行初始化。当该变量为负数时，表示容器正在进行扩容。</p>
<p>在initTable方法中，首先会判断sizeCtl的值，如果小于0，证明其他线程正在进行扩容或初始化，此时该线程会让出cpu。</p>
<p>如果sizeCtl大于0，则进行容器的初始化。这里使用了CAS操作，在不使用sychronized锁的情况下进行初始化。初始化完成后，将sizeCtl记录的就变成了扩容阈值(触发扩容的临界条件)</p>
<h2 id="ConcurrentHashMap的存值操作-CAS加自旋"><a href="#ConcurrentHashMap的存值操作-CAS加自旋" class="headerlink" title="ConcurrentHashMap的存值操作 CAS加自旋"></a><strong>ConcurrentHashMap的存值操作</strong> CAS加自旋</h2><p>ConcurrentHashMap的put方法<br><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//put操作</span></span><br><span class="line">public V put(K key, V value) &#123;</span><br><span class="line">    <span class="keyword">return</span> putVal(key, value, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">final V putVal(K key, V value, <span class="built_in">boolean</span> onlyIfAbsent) &#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="literal">null</span> || value == <span class="literal">null</span>) throw <span class="keyword">new</span> NullPointerException();<span class="comment">//不允许为null</span></span><br><span class="line">    int <span class="built_in">hash</span> = spread(key.hashCode());</span><br><span class="line">    int binCount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Node&lt;K,V&gt;[] <span class="literal">tab</span> = table;;) &#123;</span><br><span class="line">        Node&lt;K,V&gt; f; int n, i, fh;</span><br><span class="line">        <span class="keyword">if</span> (<span class="literal">tab</span> == <span class="literal">null</span> || (n = <span class="literal">tab</span>.<span class="built_in">length</span>) == <span class="number">0</span>)</span><br><span class="line">            <span class="literal">tab</span> = initTable();</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((f = tabAt(<span class="literal">tab</span>, i = (n - <span class="number">1</span>) &amp; <span class="built_in">hash</span>)) == <span class="literal">null</span>) &#123;<span class="comment">//tabAt利用hash去取数组中这个下标对应的值，取值的操作是一个CAS操作，且保证可见性</span></span><br><span class="line">            <span class="keyword">if</span> (casTabAt(<span class="literal">tab</span>, i, <span class="literal">null</span>,</span><br><span class="line">                            <span class="keyword">new</span> Node&lt;K,V&gt;(<span class="built_in">hash</span>, key, value, <span class="literal">null</span>)))</span><br><span class="line">                <span class="keyword">break</span>;                   <span class="comment">// no lock when adding to empty bin</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((fh = f.<span class="built_in">hash</span>) == MOVED)</span><br><span class="line">            <span class="literal">tab</span> = helpTransfer(<span class="literal">tab</span>, f);</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            V oldVal = <span class="literal">null</span>;</span><br><span class="line">            synchronized (f) &#123;<span class="comment">//这里的f是算出来的hash值所在的数组的链的头节点。</span></span><br><span class="line">                <span class="keyword">if</span> (tabAt(<span class="literal">tab</span>, i) == f) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (fh &gt;= <span class="number">0</span>) &#123;<span class="comment">//fh大于0证明是一个链表</span></span><br><span class="line">                        binCount = <span class="number">1</span>;</span><br><span class="line">                        <span class="keyword">for</span> (Node&lt;K,V&gt; e = f;; ++binCount) &#123;<span class="comment">//找到数组对应的下标后，依次遍历链表</span></span><br><span class="line">                            K ek;</span><br><span class="line">                            <span class="keyword">if</span> (e.<span class="built_in">hash</span> == <span class="built_in">hash</span> &amp;&amp;</span><br><span class="line">                                ((ek = e.key) == key ||</span><br><span class="line">                                    (ek != <span class="literal">null</span> &amp;&amp; key.<span class="built_in">equals</span>(ek)))) &#123;</span><br><span class="line">                                oldVal = e.val;</span><br><span class="line">                                <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                    e.val = value;</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                            Node&lt;K,V&gt; pred = e;</span><br><span class="line">                            <span class="keyword">if</span> ((e = e.next) == <span class="literal">null</span>) &#123;</span><br><span class="line">                                pred.next = <span class="keyword">new</span> Node&lt;K,V&gt;(<span class="built_in">hash</span>, key,</span><br><span class="line">                                                            value, <span class="literal">null</span>);</span><br><span class="line">                                <span class="keyword">break</span>;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (f instanceof TreeBin) &#123;<span class="comment">//证明是一棵二叉树</span></span><br><span class="line">                        Node&lt;K,V&gt; p;</span><br><span class="line">                        binCount = <span class="number">2</span>;</span><br><span class="line">                        <span class="keyword">if</span> ((p = ((TreeBin&lt;K,V&gt;)f).putTreeVal(<span class="built_in">hash</span>, key,</span><br><span class="line">                                                        value)) != <span class="literal">null</span>) &#123;</span><br><span class="line">                            oldVal = p.val;</span><br><span class="line">                            <span class="keyword">if</span> (!onlyIfAbsent)</span><br><span class="line">                                p.val = value;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (binCount != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (binCount &gt;= TREEIFY_THRESHOLD)<span class="comment">//判断链表长度是否大于8，大于8的话再判断数组长度是否大于64。如果都满足，则将链表变为红黑树。如果链表长度大于8，数组长度不大于64，则先进行扩容。</span></span><br><span class="line">                    treeifyBin(<span class="literal">tab</span>, i);</span><br><span class="line">                <span class="keyword">if</span> (oldVal != <span class="literal">null</span>)</span><br><span class="line">                    <span class="keyword">return</span> oldVal;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    addCount(<span class="number">1</span>L, binCount);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>synchronized (f)：锁住的是头节点，当多个线程添加元素，算出来的hash值都是一个数组所对应的下标时，通过锁机制，利用sychronized锁让线程按顺序执行，保证了线程安全。同时，当多个线程同时进行添加操作，但多个线程添加的元素的hash值并不是同一个hash值时，多个线程添加元素将不会收到影响，同时也不会对线程安全产生影响，提高了添加元素的效率。</p>
<p>在放入元素最后，调用了addCount方法，来计算和维护这个集合的长度。</p>
<h2 id="如何计算-ConcurrentHashMap-Size"><a href="#如何计算-ConcurrentHashMap-Size" class="headerlink" title="如何计算 ConcurrentHashMap Size"></a><strong>如何计算 ConcurrentHashMap Size</strong></h2><p>在实际的项目过程中，我们通常需要获取集合类的长度， 那么计算 ConcurrentHashMap 的元素大小就是一个有趣的问题，因为他是并发操作的，就是在你计算 size 的时候，它还在并发的插入数据，可能会导致你计算出来的 size 和你实际的 size 有差距。本文主要分析下 JDK1.8 的实现。 关于 JDK1.7 简单提一下。</p>
<p>在 JDK1.7 中，第一种方案他会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size，最多三次，比较前后两次计算的结果，结果一致就认为当前没有元素加入，计算的结果是准确的。 第二种方案是如果第一种方案不符合，他就会给每个 Segment 加上锁，然后计算 ConcurrentHashMap 的 size 返回。其源码实现:</p>
<figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="type">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">final</span> Segment&lt;K,V&gt;[] segments = <span class="keyword">this</span>.segments;</span><br><span class="line">  <span class="type">int</span> size;</span><br><span class="line">  <span class="type">boolean</span> overflow; <span class="comment">// true if size overflows 32 bits</span></span><br><span class="line">  <span class="type">long</span> sum;         <span class="comment">// sum of modCounts</span></span><br><span class="line">  <span class="type">long</span> last = <span class="number">0L</span>;   <span class="comment">// previous sum</span></span><br><span class="line">  <span class="type">int</span> retries = <span class="number">-1</span>; <span class="comment">// first iteration isn&#x27;t retry</span></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">      <span class="keyword">if</span> (retries++ == RETRIES_BEFORE_LOCK) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; segments.length; ++j)</span><br><span class="line">          <span class="built_in">ensureSegment</span>(j).<span class="built_in">lock</span>(); <span class="comment">// force creation</span></span><br><span class="line">      &#125;</span><br><span class="line">      sum = <span class="number">0L</span>;</span><br><span class="line">      size = <span class="number">0</span>;</span><br><span class="line">      overflow = <span class="literal">false</span>;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; segments.length; ++j) &#123;</span><br><span class="line">        Segment&lt;K,V&gt; seg = <span class="built_in">segmentAt</span>(segments, j);</span><br><span class="line">        <span class="keyword">if</span> (seg != null) &#123;</span><br><span class="line">          sum += seg.modCount;</span><br><span class="line">          <span class="type">int</span> c = seg.count;</span><br><span class="line">          <span class="keyword">if</span> (c &lt; <span class="number">0</span> || (size += c) &lt; <span class="number">0</span>)</span><br><span class="line">            overflow = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">if</span> (sum == last)</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      last = sum;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; finally &#123;</span><br><span class="line">    <span class="keyword">if</span> (retries &gt; RETRIES_BEFORE_LOCK) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; segments.length; ++j)</span><br><span class="line">        <span class="built_in">segmentAt</span>(segments, j).<span class="built_in">unlock</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> overflow ? Integer.MAX_VALUE : size;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>JDK1.8 实现相比 JDK 1.7 简单很多，只有一种方案，我们直接看 size() 代码：</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">int</span> <span class="title">size</span>()</span> &#123;</span><br><span class="line">    <span class="built_in">long</span> n = sumCount();</span><br><span class="line">    <span class="keyword">return</span> ((n &lt; <span class="number">0L</span>) ? <span class="number">0</span> :</span><br><span class="line">           (n &gt; (<span class="built_in">long</span>)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (<span class="built_in">int</span>)n);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最大值是 Integer 类型的最大值，但是 Map 的 size 可能超过 MAX_VALUE， 所以还有一个方法 <code>mappingCount()</code>，JDK 的建议使用 <code>mappingCount()</code> 而不是<code>size()</code>。<code>mappingCount()</code> 的代码如下：</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">mappingCount</span>()</span> &#123;</span><br><span class="line">    <span class="built_in">long</span> n = sumCount();</span><br><span class="line">    <span class="keyword">return</span> (n &lt; <span class="number">0L</span>) ? <span class="number">0L</span> : n; <span class="comment">// ignore transient negative values</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以上可以看出，无论是 <code>size()</code> 还是 <code>mappingCount()</code>, 计算大小的核心方法都是 <code>sumCount()</code>。<code>sumCount()</code> 的代码如下:</p>
<figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="built_in">long</span> sumCount() &#123;</span><br><span class="line">    CounterCell[] <span class="keyword">as</span> = counterCells; </span><br><span class="line">    CounterCell a;</span><br><span class="line">    <span class="built_in">long</span> <span class="keyword">sum</span> = baseCount;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">as</span> != <span class="literal">null</span>) &#123;</span><br><span class="line">       <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="keyword">as</span>.length; ++i) &#123;</span><br><span class="line">           <span class="keyword">if</span> ((a = <span class="keyword">as</span>[i]) != <span class="literal">null</span>)</span><br><span class="line">               <span class="keyword">sum</span> += a.value;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">sum</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>分析一下<code>sumCount()</code>代码。ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。<code>sumCount()</code> 就是迭代 counterCells 来统计 sum 的过程。 put 操作时，肯定会影响 <code>size()</code>，在 <code>put()</code> 方法最后会调用 <code>addCount()</code> 方法。</p>
<p><code>addCount</code>方法中的<code>baseCount</code>和<code>counterCells</code>的和是该集合的长度</p>
<p>addCount() 代码如下：</p>
<p>如果 counterCells == null, 则对 baseCount 做 CAS 自增操作。<br><figure class="highlight axapta"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">void</span> addCount(<span class="built_in">long</span> x, <span class="built_in">int</span> check) &#123;</span><br><span class="line">    CounterCell[] <span class="keyword">as</span>; <span class="built_in">long</span> b, s;</span><br><span class="line">    <span class="keyword">if</span> ((<span class="keyword">as</span> = counterCells) != <span class="literal">null</span> ||</span><br><span class="line">        !U.compareAndSwapLong(<span class="keyword">this</span>, BASECOUNT, b = baseCount, s = b + x)) &#123;</span><br><span class="line">        CounterCell a; <span class="built_in">long</span> v; <span class="built_in">int</span> m;</span><br><span class="line">        <span class="built_in">boolean</span> uncontended = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">as</span> == <span class="literal">null</span> || (m = <span class="keyword">as</span>.length - <span class="number">1</span>) &lt; <span class="number">0</span> ||</span><br><span class="line">            (a = <span class="keyword">as</span>[ThreadLocalRandom.getProbe() &amp; m]) == <span class="literal">null</span> ||</span><br><span class="line">            !(uncontended =</span><br><span class="line">                U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) &#123;</span><br><span class="line">            fullAddCount(x, uncontended);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (check &lt;= <span class="number">1</span>)</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        s = sumCount();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (check &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        Node&lt;K,V&gt;[] tab, nt; <span class="built_in">int</span> n, sc;</span><br><span class="line">        <span class="keyword">while</span> (s &gt;= (<span class="built_in">long</span>)(sc = sizeCtl) &amp;&amp; (tab = table) != <span class="literal">null</span> &amp;&amp;</span><br><span class="line">                (n = tab.length) &lt; MAXIMUM_CAPACITY) &#123;</span><br><span class="line">            <span class="built_in">int</span> rs = resizeStamp(n);</span><br><span class="line">            <span class="keyword">if</span> (sc &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> ((sc &gt;&gt;&gt; RESIZE_STAMP_SHIFT) != rs || sc == rs + <span class="number">1</span> ||</span><br><span class="line">                    sc == rs + MAX_RESIZERS || (nt = nextTable) == <span class="literal">null</span> ||</span><br><span class="line">                    transferIndex &lt;= <span class="number">0</span>)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc, sc + <span class="number">1</span>))</span><br><span class="line">                    transfer(tab, nt);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (U.compareAndSwapInt(<span class="keyword">this</span>, SIZECTL, sc,</span><br><span class="line">                                            (rs &lt;&lt; RESIZE_STAMP_SHIFT) + <span class="number">2</span>))</span><br><span class="line">                transfer(tab, <span class="literal">null</span>);</span><br><span class="line">            s = sumCount();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>当多个线程添加完元素都来访问<code>baseCount</code>，此时进行CAS操作</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> ((<span class="keyword">as</span> = counterCells) != null<span class="operator"> ||</span></span><br><span class="line"><span class="operator">        </span>!<span class="module-access"><span class="module"><span class="identifier">U</span>.</span></span>compare<span class="constructor">AndSwapLong(<span class="params">this</span>, BASECOUNT, <span class="params">b</span> = <span class="params">baseCount</span>, <span class="params">s</span> = <span class="params">b</span> + <span class="params">x</span>)</span>)</span><br></pre></td></tr></table></figure>
<p>当这个函数是第一次被执行的时候,<code>counterCells</code>默认是null，此时会执行if里面的语句，if里面的语句会对<code>counterCells</code>数组进行创建，并进行初始化。</p>
<p>当这个函数不是第一次被执行的时候，执行CAS操作，CAS操作会对<code>baseCount</code>进行+1操作，当多个线程同时访问<code>baseCount</code>时，只会有一个操作成功，其他线程会进入if里面的语句，对<code>counterCells</code>数组中的某个下标的值进行加1操作。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/28/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%9D%A2%E8%AF%95%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/28/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%9D%A2%E8%AF%95%E9%A2%98/" class="post-title-link" itemprop="url">多线程面试题</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-28 16:31:14" itemprop="dateCreated datePublished" datetime="2022-04-28T16:31:14+08:00">2022-04-28</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-29 21:38:52" itemprop="dateModified" datetime="2022-04-29T21:38:52+08:00">2022-04-29</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="JVM虚拟机性能调优"><a href="#JVM虚拟机性能调优" class="headerlink" title="JVM虚拟机性能调优"></a><strong>JVM虚拟机性能调优</strong></h2><img src="/wang-cheng/2022/04/28/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%9D%A2%E8%AF%95%E9%A2%98/JDK.png" class title="JDK">

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/" class="post-title-link" itemprop="url">面试题(八)</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-24 13:01:45" itemprop="dateCreated datePublished" datetime="2022-04-24T13:01:45+08:00">2022-04-24</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-26 16:52:11" itemprop="dateModified" datetime="2022-04-26T16:52:11+08:00">2022-04-26</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h1 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h1><h2 id="Redis线程模式，单线程为什么这么快"><a href="#Redis线程模式，单线程为什么这么快" class="headerlink" title="Redis线程模式，单线程为什么这么快"></a><strong>Redis线程模式，单线程为什么这么快</strong></h2><p>Redis基于Reactor模式开发了网络事件处理器，这个处理器叫做文件事件处理器 file event handler。</p>
<p>这个文件事件处理器，它是单线程的，所以 Redis 才叫做单线程的模型，<strong>它采用IO多路复用机制来同时监听多个Socket，根据Socket上的事件类型来选择对应的事件处理器来处理这个事件</strong>。可以实现高性能的网络通信模型，又可以跟内部其他单线程的模块进行对接，保证了 Redis 内部的线程模型的简单性。</p>
<p>文件事件处理器的结构包含4个部分：多个Socket、IO多路复用程序、文件事件分派器以及事件处理器（命令请求处理器、命令回复处理器、连接应答处理器等）。</p>
<p>多个 Socket 可能并发的产生不同的操作，每个操作对应不同的文件事件，但是IO多路复用程序会监听多个 Socket，会将 Socket 放入一个队列中排队，每次从队列中取出一个 Socket 给事件分派器，事件分派器把 Socket 给对应的事件处理器。</p>
<p>然后一个 Socket 的事件处理完之后，IO多路复用程序才会将队列中的下一个 Socket 给事件分派器。文件事件分派器会根据每个 Socket 当前产生的事件，来选择对应的事件处理器来处理。</p>
<p>什么时候适合使用多线程，什么时候适合使用单线程？</p>
<p>当一个请求需要处理很长时间或者操作很复杂，涉及到IO操作等，这个时候用多线程</p>
<p>当一个请求处理很快，但请求数量很多，这个时候用单线程</p>
<p>单线程快的原因：</p>
<ol>
<li>纯内存操作(内存操作速度很快)</li>
<li>核心是基于非阻塞的IO多路复用机制(因为主线程只会进行一些命令操作，不会进行IO操作，因为IO操作是往硬盘写数据，而这个功能是由RDB的fork线程来做的)</li>
<li>单线程反而避免了多线程的频繁上下文切换带来的性能问题</li>
</ol>
<h2 id="缓存雪崩、缓存穿透、缓存击穿、缓存降级"><a href="#缓存雪崩、缓存穿透、缓存击穿、缓存降级" class="headerlink" title="缓存雪崩、缓存穿透、缓存击穿、缓存降级"></a><strong>缓存雪崩、缓存穿透、缓存击穿、缓存降级</strong></h2><p>缓存雪崩是指缓存同一时间大面积的失效，或者是Redis发生宕机，Redis重启，所以后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>
<p><strong>解决方案</strong>：</p>
<ul>
<li><p>缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。</p>
</li>
<li><p>给每一个缓存数据增加相应的缓存标记，记录缓存是否失效，如果缓存标记失效，则更新数据缓存。(比较消耗内存，需要一直监控，但它会很快将数据补足，防止请求打到数据库上)</p>
</li>
<li><p>缓存预热(Redis重启之前，写一个接口，将热点数据放到缓存中，做一个缓存预热)</p>
</li>
<li><p>互斥锁(查一个缓存，发现缓存失效了，那么我要去查数据库，这个时候我要加一个锁，查数据库的时候，我把缓存的这个键锁起来，查完数据库再把它放到缓存中，然后释放这个锁。这样可以避免大量的请求对同一个键进行大量操作)</p>
</li>
</ul>
<p>缓存穿透是指缓存和数据库中都没有的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉(这种场景一般来自黑客攻击。因为正常用户请求一次发现没有数据就一般不会再次请求；而且缓存和数据库中都没有的数据的场景也不多，当然秒杀系统也是有可能产生缓存穿透的)</p>
<p><strong>解决方案：</strong></p>
<ul>
<li><p>接口层增加校验，如用户鉴权校验，id做基础校验，id&lt;=0的直接拦截(参数校验，将不合理的参数拦截下来)</p>
</li>
<li><p>从缓存取不到的数据，在数据库中也没有取到，这时也可以将key-value对写为key-null，缓存有效时间可以设置短点，如30秒（设置太长会导致正常情况也没法使用）。这样可以防止攻击用户反复用同一个id暴力攻击</p>
</li>
<li><p>采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的 bitmap 中，一个一定不存在的数据会被这个 bitmap 拦截掉(存在的数据可能会被拦截掉，所以这里有一个误差，误差的计算方式在算法笔记中有记录)，从而避免了对底层存储系统的查询压力</p>
</li>
</ul>
<p>缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多，时读缓存没读到数据，又同时去数据库去取数据，引起数据库压力瞬间增大，造成过大压力。和缓存雪崩不同的是，缓存击穿指并发查同一条数据(针对同一条数据)，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。</p>
<p><strong>解决方案</strong></p>
<ul>
<li><p>设置热点数据永远不过期。</p>
</li>
<li><p>加互斥锁</p>
</li>
</ul>
<p>缓存降级是指缓存失效或缓存服务器挂掉的情况下，不去访问数据库，直接返回默认数据或访问服务的内存数据。</p>
<p>在项目实战中通常会将部分热点数据缓存到服务的内存中，这样一旦缓存出现异常，可以直接使用服务的内存数据，从而避免数据库遭受巨大压力。</p>
<p>降级一般是有损的操作，所以尽量减少降级对于业务的影响程度。</p>
<h2 id="简述Redis事务实现-ACID"><a href="#简述Redis事务实现-ACID" class="headerlink" title="简述Redis事务实现 ACID"></a><strong>简述Redis事务实现 ACID</strong></h2><p>ACID指的是原子性、持久性、一致性和隔离性</p>
<p>1、事务开始</p>
<p>MULTI命令的执行，(执行这个命令之前可以执行watch命令来监控一个key)标识着一个事务的开始。MULTI命令会将客户端状态的 flags 属性中打开REDIS_MULTI 标识(标识打开意味着事务开始)来完成的。</p>
<p>2、命令入队<br>当一个客户端切换到事务状态之后，服务器会根据这个客户端发送来的命令来执行不同的操作。如果客户端发送的命令为MULTI、EXEC(执行事务中的命令)、WATCH、DISCARD(终止事务)中的一个(这四个命令并不会被当成事务命令去执行)，立即执行这个命令，否则将命令放入一个事务队列里面，然后向客户端返回 QUEUED 回复</p>
<ul>
<li><p>如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中一个，那么服务器立即执行这个命令。</p>
</li>
<li><p>如果客户端发送的是四个命令以外的其他命令，那么服务器并不立即执行这个命令。<br>首先检查此命令的格式是否正确(检查语法)，如果不正确，服务器会在客户端状态（redisClient）的 flags 属性关闭 REDIS_MULTI 标识(事务结束)，并且返回错误信息给客户端。如果正确，将这个命令放入一个事务队列里面，然后向客户端返回 QUEUED 回复</p>
</li>
</ul>
<p>事务队列是按照FIFO的方式保存入队的命令</p>
<p>3、事务执行</p>
<p>客户端发送 EXEC 命令，服务器执行 EXEC 命令逻辑。</p>
<ul>
<li><p>如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识，或者包含 REDIS_DIRTY_CAS 或者REDIS_DIRTY_EXEC 标识，那么就直接取消事务的执行</p>
</li>
<li><p>否则客户端处于事务状态（flags 有 REDIS_MULTI 标识），服务器会遍历客户端的事务队列，然后执行事务队列中的所有命令，最后将返回结果全部返回给客户端；</p>
</li>
</ul>
<p>redis 不支持事务回滚机制，但是它会检查每一个事务中的命令是否错误</p>
<p>Redis 事务不支持检查那些程序员自己逻辑错误。例如对 String 类型的数据库键执行对 HashMap 类型的操作！</p>
<ul>
<li><p>WATCH 命令是一个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）行为。可以监控一个或多个键，一旦其中有一个键被修改（或删除），之后的事务就不会执行，监控一直持续到EXEC命令。</p>
</li>
<li><p>MULTI命令用于开启一个事务，它总是返回OK。MULTI执行之后，客户端可以继续向服务器发送任意多条命令，这些命令不会立即被执行，而是被放到一个队列中，当EXEC命令被调用时，所有队列中的命令才会被执行。</p>
</li>
<li><p>EXEC：执行所有事务块内的命令。返回事务块内所有命令的返回值，按命令执行的先后顺序排列。当操作被打断时，返回空值 nil 。</p>
</li>
<li><p>通过调用DISCARD，客户端可以清空事务队列，并放弃执行事务， 并且客户端会从事务状态中退出。</p>
</li>
<li><p>UNWATCH命令可以取消watch对所有key的监控</p>
</li>
</ul>
<h2 id="Redis集群方案"><a href="#Redis集群方案" class="headerlink" title="Redis集群方案"></a><strong>Redis集群方案</strong></h2><p>主从</p>
<p><strong>哨兵模式：</strong>(基于主从模式)</p>
<p>sentinel，哨兵是 redis 集群中非常重要的一个组件，主要有以下功能：</p>
<ul>
<li><p>集群监控：负责监控 redis master 和 slave 进程是否正常工作。</p>
</li>
<li><p>消息通知：如果某个 redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。</p>
</li>
<li><p>故障转移：如果 master node 挂掉了，会自动转移到 slave node 上。</p>
</li>
<li><p>配置中心：如果故障转移发生了，通知 client 客户端新的 master 地址。</p>
</li>
</ul>
<p>哨兵用于实现 redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。</p>
<ul>
<li><p>故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举</p>
</li>
<li><p>即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的</p>
</li>
<li><p>哨兵通常需要 3 个实例，来保证自己的健壮性</p>
</li>
<li><p>哨兵 + redis 主从的部署架构，是不保证数据零丢失的，只能保证 redis 集群的高可用性</p>
</li>
<li><p>对于哨兵 + redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。</p>
</li>
</ul>
<h2 id="redis-主从复制的核心原理"><a href="#redis-主从复制的核心原理" class="headerlink" title="redis 主从复制的核心原理"></a><strong>redis 主从复制的核心原理</strong></h2><p>主节点可以进行读写操作、从节点只可以进行读操作，从节点的数据都是从主节点拷贝来的，通过一些机制来进行拷贝。</p>
<p>通过执行slaveof命令或设置slaveof选项，让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作，当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的，并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库，而一个从数据库只能拥有一个主数据库。</p>
<p>全量复制：</p>
<p>（1）主节点通过bgsave命令fork子进程进行RDB持久化，该过程是非常消耗CPU、内存(页表复制)、硬盘IO的</p>
<p>（2）主节点通过网络将RDB文件发送给从节点，对主从节点的带宽都会带来很大的消耗</p>
<p>（3）从节点清空老数据、载入新RDB文件的过程是阻塞的，无法响应客户端的命令；如果从节点执行bgrewriteaof，也会带来额外的消耗</p>
<p>部分复制：</p>
<ol>
<li><p>复制偏移量：执行复制的双方，主从节点，分别会维护一个复制偏移量offset</p>
</li>
<li><p>复制积压缓冲区：主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区，当主从节点offset的差距过大超过缓冲区长度时，将无法执行部分复制，只能执行全量复制。</p>
</li>
<li><p>服务器运行ID(runid)：每个Redis节点，都有其运行ID，运行ID由节点在启动时自动生成，主节点会将自己的运行ID发送给从节点，从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候，就是根据运行ID来判断同步的进度</p>
<ul>
<li><p>如果从节点保存的runid与主节点现在的runid相同，说明主从节点之前同步过，主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况)；</p>
</li>
<li><p>如果从节点保存的runid与主节点现在的runid不同，说明从节点在断线前同步的Redis节点并不是当前的主节点，只能进行全量复制</p>
</li>
</ul>
</li>
</ol>
<p>过程原理：</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6.png" class title="主从复制">
<h2 id="一个字符串类型的值能存储最大容量是多少？"><a href="#一个字符串类型的值能存储最大容量是多少？" class="headerlink" title="一个字符串类型的值能存储最大容量是多少？"></a><strong>一个字符串类型的值能存储最大容量是多少？</strong></h2><p>查询官方文档（<a target="_blank" rel="noopener" href="https://redis.io/topics/data-types）">https://redis.io/topics/data-types）</a></p>
<p>可以看到String类型的value值最多支持的长度为512M，所以正确的答案是512M。</p>
<h2 id="能说一下Redis每种数据结构的使用场景吗？"><a href="#能说一下Redis每种数据结构的使用场景吗？" class="headerlink" title="能说一下Redis每种数据结构的使用场景吗？"></a><strong>能说一下Redis每种数据结构的使用场景吗？</strong></h2><h3 id="（1）String的使用场景"><a href="#（1）String的使用场景" class="headerlink" title="（1）String的使用场景"></a><strong>（1）String的使用场景</strong></h3><pre><code>字符串类型的使用场景：信息缓存、计数器、分布式锁等等。
</code></pre><p><strong>实战场景1：记录每一个用户的访问次数，或者记录每一个商品的浏览次数</strong></p>
<p>方案：</p>
<p>常用键名：userid:pageview 或者 pageview:userid，如果一个用户的id为123，那对应的redis key就为pageview:123，value就为用户的访问次数，增加次数可以使用命令：incr。</p>
<p>使用理由：每一个用户访问次数或者商品浏览次数的修改是很频繁的，如果使用mysql这种文件系统频繁修改会造成mysql压力，效率也低。而使用redis的好处有二：使用内存，很快；单线程，所以无竞争，数据不会被改乱。</p>
<p><strong>实战场景2：缓存频繁读取，但是不常修改的信息，如用户信息，视频信息</strong></p>
<p>方案：</p>
<p>业务逻辑上：先从redis读取，有值就从redis读取，没有则从mysql读取，并写一份到redis中作为缓存，注意要设置过期时间。</p>
<p>键值设计上：</p>
<p>直接将用户一条mysql记录做序列化(通常序列化为json)作为值，userInfo:userid 作为key，键名如：userInfo:123，value存储对应用户信息的json串。如 key为：”user:id:name:1”,  value为”{“name”:”leijia”,”age”:18}”。</p>
<p><strong>实战场景3：限定某个ip特定时间内的访问次数</strong></p>
<p>方案：</p>
<p>用key记录IP，value记录访问次数，同时key的过期时间设置为60秒，如果key过期了则重新设置，否则进行判断，当一分钟内访问超过100次，则禁止访问。</p>
<p><strong>实战场景4:分布式session</strong></p>
<p>我们知道session是以文件的形式保存在服务器中的；如果你的应用做了负载均衡，将网站的项目放在多个服务器上，当用户在服务器A上进行登陆，session文件会写在A服务器；当用户跳转页面时，请求被分配到B服务器上的时候，就找不到这个session文件，用户就要重新登陆。</p>
<p>如果想要多个服务器共享一个session，可以将session存放在redis中，redis可以独立于所有负载均衡服务器，也可以放在其中一台负载均衡服务器上；但是所有应用所在的服务器连接的都是同一个redis服务器。</p>
<h3 id="（2）Hash的使用场景"><a href="#（2）Hash的使用场景" class="headerlink" title="（2）Hash的使用场景"></a><strong>（2）Hash的使用场景</strong></h3><p>以购物车为例子，用户id设置为key，那么购物车里所有的商品就是用户key对应的值了，每个商品有id和购买数量，对应hash的结构就是商品id为field，商品数量为value。</p>
<h3 id="（3）List的使用场景"><a href="#（3）List的使用场景" class="headerlink" title="（3）List的使用场景"></a><strong>（3）List的使用场景</strong></h3><p>列表本质是一个有序的，元素可重复的队列。</p>
<p><strong>实战场景：定时排行榜</strong></p>
<p>list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中，如QQ音乐内地排行榜，每周计算一次存储再list类型中，访问接口时通过page和size分页转化成lrange命令获取排行榜数据。</p>
<p>但是，并不是所有的排行榜都能用list类型实现，只有定时计算的排行榜才适合使用list类型存储，与定时计算的排行榜相对应的是实时计算的排行榜，list类型不能支持实时计算的排行榜，下面介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。</p>
<h3 id="（4）Set的使用场景"><a href="#（4）Set的使用场景" class="headerlink" title="（4）Set的使用场景"></a><strong>（4）Set的使用场景</strong></h3><p>集合的特点是无序性和确定性（不重复）。</p>
<p><strong>实战场景：收藏夹</strong></p>
<p>例如QQ音乐中如果你喜欢一首歌，点个『喜欢』就会将歌曲放到个人收藏夹中，每一个用户做一个收藏的集合，每个收藏的集合存放用户收藏过的歌曲id。</p>
<p>key为用户id，value为歌曲id的集合。</p>
<h3 id="（5）Sorted-Set的使用场景"><a href="#（5）Sorted-Set的使用场景" class="headerlink" title="（5）Sorted Set的使用场景"></a><strong>（5）Sorted Set的使用场景</strong></h3><p>有序集合的特点是有序，无重复值。与set不同的是sorted set每个元素都会关联一个score属性，redis正是通过score来为集合中的成员进行从小到大的排序。</p>
<p><strong>实战场景：实时排行榜</strong></p>
<p>QQ音乐中有多种实时榜单，比如飙升榜、热歌榜、新歌榜，可以用redis key存储榜单类型，score为点击量，value为歌曲id，用户每点击一首歌曲会更新redis数据，sorted set会依据score即点击量将歌曲id排序。</p>
<h2 id="Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？"><a href="#Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？" class="headerlink" title="Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？"></a><strong>Redis如何做持久化的？能说一下RDB和AOF的实现原理吗？</strong></h2><p>什么是持久化？</p>
<p>持久化（Persistence），即把数据（如内存中的对象）保存到可永久保存的存储设备中（如磁盘）。持久化的主要应用是将内存中的对象存储在数据库中，或者存储在磁盘文件中、XML数据文件中等等。</p>
<h3 id="Redis如何实现持久化？"><a href="#Redis如何实现持久化？" class="headerlink" title="Redis如何实现持久化？"></a><strong>Redis如何实现持久化？</strong></h3><p>Redis官方提供了不同级别的持久化方式：</p>
<ul>
<li><p>RDB持久化：能够在指定的时间间隔能对你的数据进行快照存储。</p>
</li>
<li><p>AOF持久化：记录每次对服务器写的操作，当服务器重启的时候会重新执行这些命令来恢复原始的数据，AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写，使得AOF文件的体积不至于过大。</p>
</li>
<li><p>不使用持久化：如果你只希望你的数据在服务器运行的时候存在，你也可以选择不使用任何持久化方式。</p>
</li>
<li><p>同时开启RDB和AOF：你也可以同时开启两种持久化方式，在这种情况下当redis重启的时候会优先载入AOF文件来恢复原始的数据，因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。</p>
</li>
</ul>
<h3 id="RDB持久化"><a href="#RDB持久化" class="headerlink" title="RDB持久化"></a><strong>RDB持久化</strong></h3><p>RDB(Redis Database)持久化是把当前内存数据生成快照保存到硬盘的过程，触发RDB持久化过程分为手动触发和自动触发。</p>
<p>（1）手动触发</p>
<p>手动触发对应save命令，会阻塞当前Redis服务器，直到RDB过程完成为止，对于内存比较大的实例会造成长时间阻塞，线上环境不建议使用。</p>
<p>（2）自动触发</p>
<p>自动触发对应bgsave命令，Redis进程执行fork操作创建子进程，RDB持久化过程由子进程负责，完成后自动结束。阻塞只发生在fork阶段，一般时间很短。</p>
<p>在redis.conf配置文件中可以配置：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">save <span class="tag">&lt;<span class="name">seconds</span>&gt;</span> <span class="tag">&lt;<span class="name">changes</span>&gt;</span></span><br></pre></td></tr></table></figure><br>表示xx秒内数据修改xx次时自动触发bgsave。如果想关闭自动触发，可以在save命令后面加一个空串，即：<br><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">save</span> <span class="string">&quot;&quot;</span></span><br></pre></td></tr></table></figure></p>
<p>还有其他常见可以触发bgsave，如：</p>
<ul>
<li><p>如果从节点执行全量复制操作，主节点自动执行bgsave生成RDB文件并发送给从节点。</p>
</li>
<li><p>默认情况下执行shutdown命令时，如果没有开启AOF持久化功能则 自动执行bgsave。</p>
</li>
</ul>
<p><strong>bgsave工作机制</strong></p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/bgsave%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.png" class title="bgsave工作机制">
<p>（1）执行bgsave命令，Redis父进程判断当前是否存在正在执行的子进 程，如RDB/AOF子进程，如果存在，bgsave命令直接返回。</p>
<p>（2）父进程执行fork操作创建子进程，fork操作过程中父进程会阻塞，通 过info stats命令查看latest_fork_usec选项，可以获取最近一个fork操作的耗时，单位为微秒</p>
<p>（3）父进程fork完成后，bgsave命令返回“Background saving started”信息并不再阻塞父进程，可以继续响应其他命令。</p>
<p>（4）子进程创建RDB文件，根据父进程内存生成临时快照文件，完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间，对应info统计的rdb_last_save_time选项。</p>
<p>（5）进程发送信号给父进程表示完成，父进程更新统计信息，具体见 info Persistence下的rdb_*相关选项。</p>
<p>— RDB持久化完 —</p>
<h3 id="AOF持久化"><a href="#AOF持久化" class="headerlink" title="AOF持久化"></a><strong>AOF持久化</strong></h3><p>AOF（append only file）持久化：以独立日志的方式记录每次写命令， 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式。</p>
<p>AOF持久化工作机制</p>
<p>开启AOF功能需要配置：appendonly yes，默认不开启。</p>
<p>AOF文件名 通过appendfilename配置设置，默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致，通过dir配置指定。</p>
<p>AOF的工作流程操作：命令写入 （append）、文件同步（sync）、文件重写（rewrite）、重启加载 （load）。</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.png" class title="AOF工作流程">
<ol>
<li><p>所有的写入命令会追加到aof_buf（缓冲区）中。</p>
</li>
<li><p>AOF缓冲区根据对应的策略向硬盘做同步操作。</p>
</li>
</ol>
<p>AOF为什么把命令追加到aof_buf中？Redis使用单线程响应命令，如果每次写AOF文件命令都直接追加到硬盘，那么性能完全取决于当前硬盘负载。先写入缓冲区aof_buf中，还有另一个好处，Redis可以提供多种缓冲区同步硬盘的策略，在性能和安全性方面做出平衡。</p>
<ol>
<li><p>随着AOF文件越来越大，需要定期对AOF文件进行重写，达到压缩的目的。</p>
</li>
<li><p>当Redis服务器重启时，可以加载AOF文件进行数据恢复。</p>
</li>
</ol>
<p><strong>AOF重写（rewrite）机制</strong></p>
<p>重写的目的：</p>
<ul>
<li>减小AOF文件占用空间；</li>
<li>更小的AOF 文件可以更快地被Redis加载恢复。</li>
</ul>
<p>AOF重写可以分为手动触发和自动触发：</p>
<ul>
<li>手动触发：直接调用bgrewriteaof命令。</li>
<li>自动触发：根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。</li>
</ul>
<p>auto-aof-rewrite-min-size：表示运行AOF重写时文件最小体积，默认 为64MB。</p>
<p>auto-aof-rewrite-percentage：代表当前AOF文件空间 （aof_current_size）和上一次重写后AOF文件空间（aof_base_size）的比值。</p>
<p>自动触发时机</p>
<p>当aof_current_size&gt;auto-aof-rewrite-minsize 并且（aof_current_size-aof_base_size）/aof_base_size&gt;=auto-aof-rewritepercentage。</p>
<p>其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。</p>
<img src="/wang-cheng/2022/04/24/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%85%AB/AOF%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D.png" class title="AOF文件数据恢复">
<p>数据恢复流程说明：</p>
<p>（1）AOF持久化开启且存在AOF文件时，优先加载AOF文件。</p>
<p>（2）AOF关闭或者AOF文件不存在时，加载RDB文件。</p>
<p>（3）加载AOF/RDB文件成功后，Redis启动成功。</p>
<p>（4）AOF/RDB文件存在错误时，Redis启动失败并打印错误信息。</p>
<p>— AOF持久化完 —</p>
<h3 id="RDB和AOF的优缺点"><a href="#RDB和AOF的优缺点" class="headerlink" title="RDB和AOF的优缺点"></a><strong>RDB和AOF的优缺点</strong></h3><p>RDB优点</p>
<ul>
<li>RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。</li>
<li>RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心，非常适用于灾难恢复。</li>
<li>RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做，父进程不需要再做其他 IO 操作，所以 RDB 持久化方式可以最大化 Redis 的性能。</li>
<li>与AOF相比,在恢复大的数据集的时候，RDB 方式会更快一些。</li>
</ul>
<p>AOF优点</p>
<ul>
<li>你可以使用不同的 fsync(fsync函数同步内存中所有已修改的文件数据到储存设备) 策略：无 fsync、每秒 fsync 、每次写的时候 fsync .使用默认的每秒 fsync 策略, Redis 的性能依然很好( fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障，你最多丢失1秒的数据。</li>
<li>AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满，写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题。</li>
<li>Redis 可以在 AOF 文件体积变得过大时，自动地在后台对 AOF 进行重写：重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的，因为 Redis 在创建新 AOF 文件的过程中，会继续将命令追加到现有的 AOF 文件里面，即使重写过程中发生停机，现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕，Redis 就会从旧 AOF 文件切换到新 AOF 文件，并开始对新 AOF 文件进行追加操作。</li>
<li>AOF 文件有序地保存了对数据库执行的所有写入操作， 这些写入操作以 Redis 协议的格式保存， 因此 AOF 文件的内容非常容易被人读懂， 对文件进行分析（parse）也很轻松。导出（export） AOF 文件也非常简单：举个例子， 如果你不小心执行了 FLUSHALL 命令， 但只要 AOF 文件未被重写， 那么只要停止服务器， 移除 AOF 文件末尾的 FLUSHALL 命令， 并重启 Redis ， 就可以将数据集恢复到 FLUSHALL 执行之前的状态。</li>
</ul>
<p>RDB缺点</p>
<ul>
<li>Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。</li>
<li>RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候, fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。</li>
</ul>
<p>AOF缺点</p>
<ul>
<li>对于相同的数据集来说，AOF 文件的体积通常要大于 RDB 文件的体积。</li>
<li>数据恢复（load）时AOF比RDB慢，通常RDB 可以提供更有保证的最大延迟时间。</li>
</ul>
<p>RDB和AOF简单对比总结</p>
<p>RDB优点：</p>
<ul>
<li>RDB 是紧凑的二进制文件，比较适合备份，全量复制等场景</li>
<li>RDB 恢复数据远快于 AOF</li>
</ul>
<p>RDB缺点：</p>
<ul>
<li>RDB 无法实现实时或者秒级持久化；</li>
<li>新老版本无法兼容 RDB 格式。</li>
</ul>
<p>AOF优点：</p>
<ul>
<li>可以更好地保护数据不丢失；</li>
<li>appen-only 模式写入性能比较高；</li>
<li>适合做灾难性的误删除紧急恢复。</li>
</ul>
<p>AOF缺点：</p>
<ul>
<li>对于同一份文件，AOF 文件要比 RDB 快照大；</li>
<li>AOF 开启后，会对写的 QPS(每秒查询率（QPS，Queries-per-second）) 有所影响，相对于 RDB 来说 写 QPS 要下降；</li>
<li>数据库恢复比较慢， 不合适做冷备。</li>
</ul>
<hr>
<h2 id="Redis的内存淘汰机制"><a href="#Redis的内存淘汰机制" class="headerlink" title="Redis的内存淘汰机制"></a><strong>Redis的内存淘汰机制</strong></h2><p>Redis内存淘汰策略是指当缓存内存不足时，通过淘汰旧数据处理新加入数据选择的策略。</p>
<p><strong>如何配置最大内存？</strong></p>
<p>（1）通过配置文件配置</p>
<p>修改redis.conf配置文件</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">maxmemory</span> <span class="number">1024</span>mb //设置Redis最大占用内存大小为<span class="number">1024</span>M</span><br></pre></td></tr></table></figure>
<p>注意：maxmemory默认配置为0，在64位操作系统下redis最大内存为操作系统剩余内存，在32位操作系统下redis最大内存为3GB。</p>
<p>（2）通过动态命令配置</p>
<p>Redis支持运行时通过命令动态修改内存大小：</p>
<figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">6379</span>&gt; config set maxmemory <span class="number">200</span>mb //设置Redis最大占用内存大小为<span class="number">200</span>M</span><br><span class="line"><span class="attribute">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">6379</span>&gt; config get maxmemory //获取设置的Redis能使用的最大内存大小</span><br><span class="line"><span class="attribute">1</span>) <span class="string">&quot;maxmemory&quot;</span></span><br><span class="line"><span class="attribute">2</span>) <span class="string">&quot;209715200&quot;</span></span><br></pre></td></tr></table></figure>
<p>淘汰策略的分类</p>
<p>Redis最大占用内存用完之后，如果继续添加数据，如何处理这种情况呢？实际上Redis官方已经定义了八种策略来处理这种情况：</p>
<p><strong>noeviction</strong></p>
<p>默认策略，对于写请求直接返回错误，不进行淘汰。</p>
<p><strong>allkeys-lru</strong></p>
<p>lru(less recently used), 最近最少使用。从所有的key中使用近似LRU算法进行淘汰。</p>
<p><strong>volatile-lru</strong></p>
<p>lru(less recently used), 最近最少使用。从设置了过期时间的key中使用近似LRU算法进行淘汰。</p>
<p><strong>allkeys-random</strong></p>
<p>从所有的key中随机淘汰。</p>
<p><strong>volatile-random</strong></p>
<p>从设置了过期时间的key中随机淘汰。</p>
<p><strong>volatile-ttl</strong></p>
<p>ttl(time to live)，在设置了过期时间的key中根据key的过期时间进行淘汰，越早过期的越优先被淘汰。</p>
<p><strong>allkeys-lfu</strong></p>
<p>lfu(Least Frequently Used)，最少使用频率。从所有的key中使用近似LFU算法进行淘汰。从Redis4.0开始支持。</p>
<p><strong>volatile-lfu</strong></p>
<p>lfu(Least Frequently Used)，最少使用频率。从设置了过期时间的key中使用近似LFU算法进行淘汰。从Redis4.0开始支持。</p>
<p>注意：当使用volatile-lru、volatile-random、volatile-ttl这三种策略时，如果没有设置过期的key可以被淘汰，则和noeviction一样返回错误。</p>
<h3 id="LRU算法"><a href="#LRU算法" class="headerlink" title="LRU算法"></a><strong>LRU算法</strong></h3><p>LRU(Least Recently Used)，即最近最少使用，是一种缓存置换算法。在使用内存作为缓存的时候，缓存的大小一般是固定的。当缓存被占满，这个时候继续往缓存里面添加数据，就需要淘汰一部分老的数据，释放内存空间用来存储新的数据。这个时候就可以使用LRU算法了。其核心思想是：如果一个数据在最近一段时间没有被用到，那么将来被使用到的可能性也很小，所以就可以被淘汰掉。</p>
<h3 id="LRU在Redis中的实现"><a href="#LRU在Redis中的实现" class="headerlink" title="LRU在Redis中的实现"></a><strong>LRU在Redis中的实现</strong></h3><p>Redis使用的是近似LRU算法，它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据，每次随机出5个（默认）key，从里面淘汰掉最近最少使用的key。</p>
<p>可以通过maxmemory-samples参数修改采样数量， 如：maxmemory-samples 10</p>
<p>maxmenory-samples配置的越大，淘汰的结果越接近于严格的LRU算法，但因此耗费的CPU也很高。</p>
<p>Redis为了实现近似LRU算法，给每个key增加了一个额外增加了一个24bit的字段，用来存储该key最后一次被访问的时间。</p>
<h3 id="Redis3-0对近似LRU的优化"><a href="#Redis3-0对近似LRU的优化" class="headerlink" title="Redis3.0对近似LRU的优化"></a><strong>Redis3.0对近似LRU的优化</strong></h3><p>Redis3.0对近似LRU算法进行了一些优化。新算法会维护一个候选池（大小为16），池中的数据根据访问时间进行排序，第一次随机选取的key都会放入池中，随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中，直到候选池被放满。当放满后，如果有新的key需要放入，则将池中最后访问时间最大（最近被访问）的移除。</p>
<p>当需要淘汰的时候，则直接从池中选取最近访问时间最小（最久没被访问）的key淘汰掉就行。</p>
<h3 id="LFU算法"><a href="#LFU算法" class="headerlink" title="LFU算法"></a><strong>LFU算法</strong></h3><p>LFU(Least Frequently Used)，是Redis4.0新加的一种淘汰策略，它的核心思想是根据key的最近被访问的频率进行淘汰，很少被访问的优先被淘汰，被访问的多的则被留下来。</p>
<p>LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法，一个key很久没有被访问到，只刚刚是偶尔被访问了一次，那么它就被认为是热点数据，不会被淘汰，而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况，因为使用一次并不会使一个key成为热点数据。</p>
<h2 id="Redis有事务机制吗？"><a href="#Redis有事务机制吗？" class="headerlink" title="Redis有事务机制吗？"></a><strong>Redis有事务机制吗？</strong></h2><p>有事务机制。Redis事务生命周期：</p>
<ul>
<li>开启事务：使用MULTI开启一个事务</li>
<li>命令入队列：每次操作的命令都会加入到一个队列中，但命令此时不会真正被执行</li>
<li>提交事务：使用EXEC命令提交事务，开始顺序执行队列中的命令</li>
</ul>
<h2 id="Redis事务到底是不是原子性的？"><a href="#Redis事务到底是不是原子性的？" class="headerlink" title="Redis事务到底是不是原子性的？"></a><strong>Redis事务到底是不是原子性的？</strong></h2><p>先看关系型数据库ACID 中关于原子性的定义：</p>
<p><strong>原子性：</strong>一个事务(transaction)中的所有操作，要么全部完成，要么全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被恢复(Rollback)到事务开始前的状态，就像这个事务从来没有执行过一样。</p>
<p>官方文档对事务的定义：</p>
<ul>
<li>事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</li>
<li>事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。EXEC 命令负责触发并执行事务中的所有命令：如果客户端在使用 MULTI 开启了一个事务之后，却因为断线而没有成功执行 EXEC ，那么事务中的所有命令都不会被执行。另一方面，如果客户端成功在开启事务之后执行 EXEC ，那么事务中的所有命令都会被执行。</li>
</ul>
<p>官方认为Redis事务是一个原子操作，这是站在执行与否的角度考虑的。但是从ACID原子性定义来看，严格意义上讲Redis事务是非原子型的，因为在命令顺序执行过程中，一旦发生命令执行错误Redis是不会停止执行然后回滚数据。</p>
<h2 id="Redis为什么不支持回滚（roll-back）？"><a href="#Redis为什么不支持回滚（roll-back）？" class="headerlink" title="Redis为什么不支持回滚（roll back）？"></a><strong>Redis为什么不支持回滚（roll back）？</strong></h2><p>在事务运行期间虽然Redis命令可能会执行失败，但是Redis依然会执行事务内剩余的命令而不会执行回滚操作。如果你熟悉mysql关系型数据库事务，你会对此非常疑惑，Redis官方的理由如下：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">只有当被调用的Redis命令有语法错误时，这条命令才会执行失败（在将这个命令放入事务队列期间，Redis能够发现此类问题），或者对某个键执行不符合其数据类型的操作：实际上，这就意味着只有程序错误才会导致Redis命令执行失败，这种错误很有可能在程序开发期间发现，一般很少在生产环境发现。支持事务回滚能力会导致设计复杂，这与Redis的初衷相违背，Redis的设计目标是功能简化及确保更快的运行速度。</span><br></pre></td></tr></table></figure>
<p>对于官方的这种理由有一个普遍的反对观点：程序有bug怎么办？但其实回归不能解决程序的bug，比如某位粗心的程序员计划更新键A，实际上最后更新了键B，回滚机制是没法解决这种人为错误的。正因为这种人为的错误不太可能进入生产系统，所以官方在设计Redis时选用更加简单和快速的方法，没有实现回滚的机制。</p>
<h2 id="什么是Redis主从复制？"><a href="#什么是Redis主从复制？" class="headerlink" title="什么是Redis主从复制？"></a><strong>什么是Redis主从复制？</strong></h2><p>主从复制，是指将一台Redis服务器的数据，复制到其他的Redis服务器。前者称为主节点(master)，后者称为从节点(slave)；数据的复制是单向的，只能由主节点到从节点。</p>
<p>主从复制的作用</p>
<ul>
<li><p>数据冗余：主从复制实现了数据的热备份，是持久化之外的一种数据冗余方式。</p>
</li>
<li><p>故障恢复：当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复；实际上是一种服务的冗余。</p>
</li>
<li><p>负载均衡：在主从复制的基础上，配合读写分离，可以由主节点提供写服务，由从节点提<br>供读服务，分担服务器负载；尤其是在写少读多的场景下，通过多个从节点分担读负载，可以大大提高Redis服务器的并发量。</p>
</li>
<li><p>高可用基石：主从复制还是哨兵和集群能够实施的基础，因此说主从复制是Redis高可用的基础。</p>
</li>
</ul>
<p>主从复制实现原理</p>
<p>主从复制过程主要可以分为3个阶段：连接建立阶段、数据同步阶段、命令传播阶段。</p>
<p>连接建立阶段</p>
<p>该阶段的主要作用是在主从节点之间建立连接，为数据同步做好准备。</p>
<p>步骤1：保存主节点信息</p>
<p>slaveof命令是异步的，在从节点上执行slaveof命令，从节点立即向客户端返回ok，从节点服务器内部维护了两个字段，即masterhost和masterport字段，用于存储主节点的ip和port信息。</p>
<p>步骤2：建立socket连接</p>
<p>从节点每秒1次调用复制定时函数replicationCron()，如果发现了有主节点可以连接，便会根据主节点的ip和port，创建socket连接。</p>
<p>从节点为该socket建立一个专门处理复制工作的文件事件处理器，负责后续的复制工作，如接收RDB文件、接收命令传播等。</p>
<p>主节点接收到从节点的socket连接后（即accept之后），为该socket创建相应的客户端状态，并将从节点看做是连接到主节点的一个客户端，后面的步骤会以从节点向主节点发送命令请求的形式来进行。</p>
<p>步骤3：发送ping命令</p>
<p>从节点成为主节点的客户端之后，发送ping命令进行首次请求，目的是：检查socket连接是否可用，以及主节点当前是否能够处理请求。</p>
<p>从节点发送ping命令后，可能出现3种情况：</p>
<p>（1）返回pong：说明socket连接正常，且主节点当前可以处理请求，复制过程继续。</p>
<p>（2）超时：一定时间后从节点仍未收到主节点的回复，说明socket连接不可用，则从节点断开socket连接，并重连。</p>
<p>（3）返回pong以外的结果：如果主节点返回其他结果，如正在处理超时运行的脚本，说明主节点当前无法处理命令，则从节点断开socket连接，并重连。</p>
<p>步骤4：身份验证</p>
<p>如果从节点中设置了masterauth选项，则从节点需要向主节点进行身份验证；没有设置该选项，则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的，auth命令的参数即为配置文件中的masterauth的值。</p>
<p>如果主节点设置密码的状态，与从节点masterauth的状态一致（一致是指都存在，且密码相同，或者都不存在），则身份验证通过，复制过程继续；如果不一致，则从节点断开socket连接，并重连。</p>
<p>步骤5：发送从节点端口信息</p>
<p>身份验证之后，从节点会向主节点发送其监听的端口号（前述例子中为6380），主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中；该端口信息除了在主节点中执行info Replication时显示以外，没有其他作用。</p>
<p>数据同步阶段</p>
<p>主从节点之间的连接建立以后，便可以开始进行数据同步，该阶段可以理解为从节点数据的初始化。具体执行的方式是：从节点向主节点发送psync命令（Redis2.8以前是sync命令），开始同步。</p>
<p>数据同步阶段是主从复制最核心的阶段，根据主从节点当前状态的不同，可以分为全量复制和部分复制，后面再讲解这两种复制方式以及psync命令的执行过程，这里不再详述。</p>
<p>命令传播阶段</p>
<p>数据同步阶段完成后，主从节点进入命令传播阶段；在这个阶段主节点将自己执行的写命令发送给从节点，从节点接收命令并执行，从而保证主从节点数据的一致性。</p>
<p>需要注意的是，命令传播是异步的过程，即主节点发送写命令后并不会等待从节点的回复；因此实际上主从节点之间很难保持实时的一致性，延迟在所难免。数据不一致的程度，与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%B7%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%B7%E9%A2%98/" class="post-title-link" itemprop="url">计算机网络刷题</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-20 14:23:46" itemprop="dateCreated datePublished" datetime="2022-04-20T14:23:46+08:00">2022-04-20</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-21 20:26:21" itemprop="dateModified" datetime="2022-04-21T20:26:21+08:00">2022-04-21</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="IP地址块为211-168-15-192-26、211-168-15-160-27和211-168-15-128-27三个地址块经聚合后可用地址数为（）？"><a href="#IP地址块为211-168-15-192-26、211-168-15-160-27和211-168-15-128-27三个地址块经聚合后可用地址数为（）？" class="headerlink" title="IP地址块为211.168.15.192/26、211.168.15.160/27和211.168.15.128/27三个地址块经聚合后可用地址数为（）？"></a><strong>IP地址块为211.168.15.192/26、211.168.15.160/27和211.168.15.128/27三个地址块经聚合后可用地址数为（）？</strong></h2><p>A. 126</p>
<p>B. 62</p>
<p>C. 128</p>
<p>D. 68</p>
<p>解析：第一个子网的第四个字节是 11 000000，第二个是 101 00000，第三个是 100 0000，加粗部分为不可变部分（子网网络号部分）。从 1～25位三个子网完全相同，而从 26～32 位三个子网的可变部分覆盖了所有情况： 1<strong>**</strong> + 01<strong>*</strong> + 00<strong>*</strong> = <strong><em>*</em></strong>，因此可构成超网，该超网的网络号为 211.168.15.128 / 25， 主机数为：27 - 2 = 126。</p>
<p>地址聚合无非是找出它们相同的部分…将两个分配的IP地址块最后一部分换算成二进制(因为只有最后一部分不相同),之后可得出新的子网掩码(子网掩码中相同的部分用1表示,不同的部分用0表示):</p>
<p>211.168.15. 11000000</p>
<p>211.168.15. 10100000</p>
<p>211.168.15. 10000000</p>
<p>可得聚合地址块为211.168.15. 10000000，即211.168.15. 128<br>掩码是IP二进制前面相同的位数，由于从 1～25位三个子网完全相同，所以经过聚合后的地址为211.168.15. 128/25<br>所以可用地址为：2^(32-25)-2=126</p>
<hr>
<h2 id="tcp套接字中不会阻塞的是？"><a href="#tcp套接字中不会阻塞的是？" class="headerlink" title="tcp套接字中不会阻塞的是？"></a><strong>tcp套接字中不会阻塞的是？</strong></h2><p>A. read</p>
<p>B. write</p>
<p>C. accept</p>
<p>D. bind</p>
<p>解析：</p>
<img src="/wang-cheng/2022/04/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%B7%E9%A2%98/TCP.png" class title="TCP">
<p>首先，服务器端启动进程，调用Socket创建一个基于TCP协议的流套接字描述符。</p>
<p>其次，服务进程调用bind命名套接字，将套接字描述符绑定到本地地址和本地端口上。</p>
<p>再次，服务器端调用listen，开始侦听客户端的Socket连接请求。</p>
<p>接下来阻塞，直到收到了客户端的connect请求，调用accept进行相应。</p>
<p>因此，不阻塞bind和listen</p>
<hr>
<h2 id="关于TCP协议以下说法正确的是：（BD）"><a href="#关于TCP协议以下说法正确的是：（BD）" class="headerlink" title="关于TCP协议以下说法正确的是：（BD）"></a><strong>关于TCP协议以下说法正确的是：（BD）</strong></h2><p>A. 通讯双方被动关闭的一方进入TIME_WAIT状态</p>
<p>B. TIME_WAIT状态会持续2个MSL</p>
<p>C. TIME_WAIT状态会持续1个MSL</p>
<p>D. 通讯双方主动关闭的一方进入TIME_WAIT状态</p>
<p>解析：</p>
<p>假设Client端发起中断连接请求，也就是发送FIN报文。Server端接到FIN报文后，意思是说”我Client端没有数据要发给你了”，但是如果你还有数据没有发送完成，则不必急着关闭Socket，可以继续发送数据。所以你先发送ACK，”告诉Client端，你的请求我收到了，但是我还没准备好，请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态，继续等待Server端的FIN报文。当Server端确定数据已发送完成，则向Client端发送FIN报文，”告诉Client端，好了，我这边数据发完了，准备好关闭连接了”。Client端收到FIN报文后，”就知道可以关闭连接了，但是他还是不相信网络，怕Server端不知道要关闭，所以发送ACK后进入TIME_WAIT状态，如果Server端没有收到ACK则可以重传。“，Server端收到ACK后，”就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复，则证明Server端已正常关闭，那好，我Client端也可以关闭连接了。Ok，TCP连接就这样关闭了！</p>
<hr>
<h2 id="OSI的哪一层通常同时使用报头和报尾进行封装（）？"><a href="#OSI的哪一层通常同时使用报头和报尾进行封装（）？" class="headerlink" title="OSI的哪一层通常同时使用报头和报尾进行封装（）？"></a><strong>OSI的哪一层通常同时使用报头和报尾进行封装（）？</strong></h2><p>A. 网络层</p>
<p>B. 传输层</p>
<p>C. 数据链路层</p>
<p>D. 表示层</p>
<p>解析：数据链路网很特殊的一点就是组帧，要在物理层通过比特流进行传播，所以在数据帧的头部和尾部都存在标识符flag的——帧头，帧尾</p>
<hr>
<h2 id="长途自动接续中对信号音的发送地点有统一规定，对于忙音的发送地点为"><a href="#长途自动接续中对信号音的发送地点有统一规定，对于忙音的发送地点为" class="headerlink" title="长途自动接续中对信号音的发送地点有统一规定，对于忙音的发送地点为"></a><strong>长途自动接续中对信号音的发送地点有统一规定，对于忙音的发送地点为</strong></h2><p>A. 长途局</p>
<p>B. 本地汇接局</p>
<p>C. 发端本地局</p>
<p>D. 终端本地局</p>
<p>解析：通信中规定长途自动接续中的忙音由发端的本地局收到终端忙信号后发出</p>
<p>长途电话流程： 主叫-》发端本地局-》发端长途局-》终端长途局-》终端本地局-》被叫</p>
<p>被叫忙时，忙音由发端的本地局发送即可。</p>
<h2 id="中继器属于-OSI-模型的哪一层？"><a href="#中继器属于-OSI-模型的哪一层？" class="headerlink" title="中继器属于 OSI 模型的哪一层？"></a><strong>中继器属于 OSI 模型的哪一层？</strong></h2><img src="/wang-cheng/2022/04/20/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%B7%E9%A2%98/OSI.jpg" class title="OSI">
<p>（1）中继器、集线器——物理层（第一层）。</p>
<p>（2）网桥、交换机——数据链路层（第二层）。</p>
<p>（3）路由器——网络层（第三层）。</p>
<p>中继器对在线路上的信号具有放大再生的功能，用于扩展局域网网段的长度（仅用于连接相同的局域网网段）。</p>
<h2 id="信号带宽是指（-）。"><a href="#信号带宽是指（-）。" class="headerlink" title="信号带宽是指（   ）。"></a><strong>信号带宽是指（   ）。</strong></h2><p>信道的带宽为信道能够达到的最大数据速率，在无线通信中，在功率一定的情况下，带宽越大传输的数据速率越高，传输距离就近；带宽越小传输的数据速率越低，传输距离就远。</p>
<p>信号带宽 ，一个信号所包含谐波的最高频率与最低频率之差，即该信号所拥有的频率范围，定义为该信号的带宽</p>
<h2 id="一台-10M-的集线器连接了-5-台-PC-，-PC-机均安装了-100M-网卡，每台-PC-能分配到的带宽最大为？"><a href="#一台-10M-的集线器连接了-5-台-PC-，-PC-机均安装了-100M-网卡，每台-PC-能分配到的带宽最大为？" class="headerlink" title="一台 10M 的集线器连接了 5 台 PC ， PC 机均安装了 100M 网卡，每台 PC 能分配到的带宽最大为？"></a><strong>一台 10M 的集线器连接了 5 台 PC ， PC 机均安装了 100M 网卡，每台 PC 能分配到的带宽最大为？</strong></h2><p>A. 10M</p>
<p>B. 2M</p>
<p>C. 20M</p>
<p>D. 100M</p>
<p>解析：交换机独占带宽，集线器共享带宽。</p>
<h2 id="计算机网络中广域网和局域网的分类是以（-）来划分的。"><a href="#计算机网络中广域网和局域网的分类是以（-）来划分的。" class="headerlink" title="计算机网络中广域网和局域网的分类是以（        ）来划分的。"></a><strong>计算机网络中广域网和局域网的分类是以（        ）来划分的。</strong></h2><p>一、局域网</p>
<p>局域网（Local Area Network），简称LAN，是指在某一区域内由多台计算机互联成的计算机组。“某一区域”指的是同一办公室、同一建筑物、同一公司和同一学校等，一般是方圆几千米以内。局域网可以实现 文件管理 、应用软件共享、 打印机共享 、扫描仪共享、工作组内的日程安排、电子邮件和传真通信服务等功能。局域网是封闭型的，可以由办公室内的两台 计算机组成 ，也可以由一个公司内的上千台计算机组成。</p>
<p>二、广域网</p>
<p>广域网（Wide Area Network），简称WAN，是一种跨越大的、地域性的 计算机网络 的集合。通常跨越省、市，甚至一个国家。广域网包括大大小小不同的子网，子网可以是局域网，也可以是小型的广域网。</p>
<p>三、局域网和广域网的区别</p>
<p>局域网是在某一区域内的，而广域网要跨越较大的地域，那么如何来界定这个区域呢？例如，一家大型公司的总公司位于北京，而分公司遍布全国各地，如果该公司将所有的分公司都通过网络联接在一起，那么一个分公司就是一个局域网，而整个总公司网络就是一个广域网。</p>
<h2 id="集线器"><a href="#集线器" class="headerlink" title="集线器"></a><strong>集线器</strong></h2><p>它的作用可以简单的理解为将一些机器连接起来组成一个局域网。而交换机（又名交换式集线器）作用与集线器大体相同。但是两者在性能上有区别：集线器采用的式共享带宽的工作方式，而交换机是独享带宽<br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">集线器(HUB)是局域网LAN中重要的部件之一，它是网络连线的连接点。集线器有多个户端口，连接计算机和服务器之类的外围设备。一个以太网数据包从一个站发送到集线器上，然后它就被广播到集线器中的其它所有端口，所以基于集线器的网络仍然是一个共享介质的LAN。智能集线器的每一个端口都可以由网络操作员从集线器管理控制台上来配置、监视、连通或解释。</span><br></pre></td></tr></table></figure></p>
<h2 id="多路复用技术"><a href="#多路复用技术" class="headerlink" title="多路复用技术"></a><strong>多路复用技术</strong></h2><p>频分多路复用 Frequency-division multiplexing，FDM</p>
<p>波分多路复用 Wavelength Division Multiplexing, WDM</p>
<p>时分多路复用 Time-Division Multiplexing，TDM</p>
<p>空分多路复用 Space Division Multiplexing, SDM</p>
<h2 id="TCP三次握手"><a href="#TCP三次握手" class="headerlink" title="TCP三次握手"></a><strong>TCP三次握手</strong></h2><p>在TCP/IP协议中，TCP协议提供可靠的连接服务，采用三次握手建立一个连接。 </p>
<p>第一次握手：建立连接时，客户端发送syn包(syn=j)到服务器，并进入SYN_SEND状态，等待服务器确认； </p>
<p>第二次握手：服务器收到syn包，必须确认客户的SYN（ack=j+1），同时自己也发送一个SYN包（syn=k），即SYN+ACK包，此时服务器进入SYN_RECV状态； </p>
<p>第三次握手：客户端收到服务器的SYN＋ACK包，向服务器发送确认包ACK(ack=k+1)，此包发送完毕，客户端和服务器进入ESTABLISHED状态，完成三次握手。 </p>
<h2 id="模块A给部署在多台机器上的模块B采用轮询的方式发起查询请求，请问这样做优势是什么？"><a href="#模块A给部署在多台机器上的模块B采用轮询的方式发起查询请求，请问这样做优势是什么？" class="headerlink" title="模块A给部署在多台机器上的模块B采用轮询的方式发起查询请求，请问这样做优势是什么？"></a><strong>模块A给部署在多台机器上的模块B采用轮询的方式发起查询请求，请问这样做优势是什么？</strong></h2><p><strong>1.减少请求模块B的频率 2.减少系统的故障率 3.减少模块A所在机器的网络IO 4.减少模块A等待结果的时间</strong></p>
<p>解析：1.模块A对模块B请求的频率取决于模块A的业务，轮询B并不能降低频率；2某一台模块B不能工作的情况下整个系统仍旧正常运行，减少故障率；3，同1，并不能降低；4，有效提高模块B的并发，减少A的等待时间</p>
<p>轮询（Polling）是一种CPU决策如何提供周边设备服务的方式，又称“程控输出入”（Programmed I/O）。轮询法的概念是，由CPU定时发出询问，依序询问每一个周边设备是否需要其服务，有即给予服务，服务结束后再问下一个周边，接着不断周而复始。轮询法实作容易，但效率偏低。</p>
<h2 id="etc-resolv-conf的用途是"><a href="#etc-resolv-conf的用途是" class="headerlink" title="/etc/resolv.conf的用途是"></a><strong>/etc/resolv.conf的用途是</strong></h2><p>A. 邮件服务的设置文件</p>
<p>B. DHCP的设置文件</p>
<p>C. DNS解析的设置文件</p>
<p>D. 网络路由的设置文件</p>
<p>解析：</p>
<p>1.DNS解析的设置文件在： /etc/resolv.conf</p>
<p>2.邮件服务的设置文件： /etc/mail.rc</p>
<p>3.DHCP的设置文件： /etc/dhcpd.conf</p>
<p>4.网络路由的设置文件： /etc/gateways</p>
<h2 id="用交换机取代集线器，冲突域的数量将会？"><a href="#用交换机取代集线器，冲突域的数量将会？" class="headerlink" title="用交换机取代集线器，冲突域的数量将会？"></a><strong>用交换机取代集线器，冲突域的数量将会？</strong></h2><p>解析：</p>
<p>交换机：一个端口是一个冲突域</p>
<p>集线器：所有端口是一个冲突域</p>
<p>隔离意味着增加。</p>
<h2 id="GSM系统中，每个载频可包括（）个信道"><a href="#GSM系统中，每个载频可包括（）个信道" class="headerlink" title="GSM系统中，每个载频可包括（）个信道"></a><strong>GSM系统中，每个载频可包括（）个信道</strong></h2><p>A. 8</p>
<p>B. 6</p>
<p>C. 4</p>
<p>D. 2</p>
<p>解析：在GSM的TDMA中，帧被定义为每个载频中所包含的8个连续的时隙（TS0-7），相当于FDMA系统中的一个频道。所以选A。</p>
<h2 id="数据传输速率的问题"><a href="#数据传输速率的问题" class="headerlink" title="数据传输速率的问题"></a><strong>数据传输速率的问题</strong></h2><p>若连接R2和R3链路的频率带宽为8kHz，信噪比为30dB，该链路实际数据传输速率约为理论最大数据传输速率的50%，则该链路的实际数据传输速率约是？</p>
<p>解析：</p>
<p>香农定理给出了带宽受限且有高斯白噪声干扰的信道的极限数据传输速率，香农定理定义为：信道的极限数据传输速率=Wlog2(1+S/N)，单位b/s。其中，S/N为信噪比，即信号的平均功率和噪声的平均功率之比，信噪比=10log10(S/N)，单位dB，当S/N=1000时，信噪比为30dB。</p>
<p>则该链路的实际数据传输速率约为50% <em> Wlog2(1+S/N)=50% </em> 8k * log2(1 + 1000)=40kbps。</p>
<hr>
<h2 id="通过改变载波信号的频率来表示数字信号1-0的方法是"><a href="#通过改变载波信号的频率来表示数字信号1-0的方法是" class="headerlink" title="通过改变载波信号的频率来表示数字信号1,0的方法是()"></a><strong>通过改变载波信号的频率来表示数字信号1,0的方法是()</strong></h2><p>移频键控也称为数字频率控制，是数字通信中使用较早的一种调制方式，基本原理是利用载波的频率变化来传递数字信息。</p>
<p>B：错误，因为载波调制太宽泛了，包括调频，调幅，调相；</p>
<p>C：错误，振幅键控（Amplitude Shift Keying，缩写为ASK）是载波的振幅随着数字基带信号而变化的数字调制。当数字基带信号为二进制时，则为二进制振幅键控（2ASK）；</p>
<p>D：错误，相移键控包括绝对调相和相对调相</p>
<h2 id="HTTP错误代码描述"><a href="#HTTP错误代码描述" class="headerlink" title="HTTP错误代码描述"></a><strong>HTTP错误代码描述</strong></h2><p>502 - 网关错误</p>
<p>404 - 无法找到文件</p>
<p>2xx – 都是成功的http请求和服务</p>
<p>403 - 服务器理解请求客户端的请求，但是拒绝执行此请求</p>
<p>500 - 服务器内部错误，无法完成请求</p>
<h2 id="IP数据报的最大长度为（）B。"><a href="#IP数据报的最大长度为（）B。" class="headerlink" title="IP数据报的最大长度为（）B。"></a><strong>IP数据报的最大长度为（）B。</strong></h2><p>IP数据包的最大长度是64K字节(65535)，因为在IP包头中用2个字节描述报文长度，2个字节所能表达的最大数字就是65535</p>
<h2 id="调制解调技术主要用于哪种数据通信中（）"><a href="#调制解调技术主要用于哪种数据通信中（）" class="headerlink" title="调制解调技术主要用于哪种数据通信中（）"></a><strong>调制解调技术主要用于哪种数据通信中（）</strong></h2><p>调制：数字信号转变为模拟信号；</p>
<p>解调：模拟信号转变为数字信号。</p>
<h2 id="http状态码"><a href="#http状态码" class="headerlink" title="http状态码"></a><strong>http状态码</strong></h2><p>1XX（信息状态码）接受请求正在处理</p>
<p>2XX（成功状态码） 请求正常处理完毕</p>
<p>3XX（重定向状态码） 需要附加操作已完成请求</p>
<p>4XX（客户端错误状态码） 服务器无法处理请求</p>
<p>5XX（服务器错误状态码） 服务器处理请求出错</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/19/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%B7%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/19/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%B7%E9%A2%98/" class="post-title-link" itemprop="url">数据库刷题</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-19 20:09:49" itemprop="dateCreated datePublished" datetime="2022-04-19T20:09:49+08:00">2022-04-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-21 15:15:05" itemprop="dateModified" datetime="2022-04-21T15:15:05+08:00">2022-04-21</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h2 id="子查询中，父查询中一般使用IN运算符的是"><a href="#子查询中，父查询中一般使用IN运算符的是" class="headerlink" title="子查询中，父查询中一般使用IN运算符的是"></a><strong>子查询中，父查询中一般使用IN运算符的是</strong></h2><p>A. 单列单值嵌套查询</p>
<p>B. 单列多值嵌套查询</p>
<p>C. 多列多值嵌套查询</p>
<p>D. 集合查询</p>
<p>解析：<br>where型子查询：指把内部查询的结果作为外层查询的比较条件。子查询：单列单值</p>
<p>from型子查询：把内层的查询结果当成临时表，供外层sql再次查询。子查询：多行多列</p>
<p>in子查询：内层查询语句仅返回一个数据列，这个数据列的值将供外层查询语句进行比较 子查询：单列多行</p>
<p>exists子查询：把外层的查询结果，拿到内层，看内层是否成立，简单来说后面的返回true,外层（也就是前面的语句）才会执行，否则不执行。</p>
<p>any子查询：只要满足内层子查询中的任意一个比较条件，就返回一个结果作为外层查询条件。</p>
<p>all子查询：内层子查询返回的结果需同时满足所有内层查询条件。</p>
<hr>
<h2 id="下列选项中都属于关系代数运算符中集合运算符的是"><a href="#下列选项中都属于关系代数运算符中集合运算符的是" class="headerlink" title="下列选项中都属于关系代数运算符中集合运算符的是"></a><strong>下列选项中都属于关系代数运算符中集合运算符的是</strong></h2><p>A. ∩、∪、−、×</p>
<p>B. ∩、∪、σ、π</p>
<p>C. ∧、∨、−、×</p>
<p>D. σ、π、¬、∩</p>
<p>解析：</p>
<p>选择（σ， selection）、投影（π， projection）、叉乘（x， cross-product）、</p>
<p>差（-, set-difference）和并（υ, union）</p>
<p>它们和SQL语句的对应关系为：</p>
<p>选择（σ， selection）相当于SQL语句中的where，表示选出满足一定条件的行。</p>
<p>如：σ rating&gt;8 (S2）相当于 select * from S2 where rating&gt;8;</p>
<p>投影（π， projection）相当于SQL语句中的select。。。distinct， 表示选择哪些列。注意：投影是会去重的！</p>
<p>如：π sname,rating (σ rating&gt;8 (S2）)相当于 select sname, rating from S2 where rating&gt;8;</p>
<p>叉乘（x， cross-product）相当于SQL语句中的from，表示穷举所有集合两边元素的组合量</p>
<p>如: AxB 相当于 select * from A, B;  注意：叉乘时两个集合不能有重名列</p>
<p>差（-, set-difference）R-S返回所有在R中而不在S中的元组</p>
<p>并（υ, union）RυS返回包含在R中或在S中的所有元组</p>
<img src="/wang-cheng/2022/04/19/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%B7%E9%A2%98/%E8%BF%90%E7%AE%97%E7%AC%A6.png" class title="运算符">
<hr>
<h2 id="下列选项中不属于结构化查询语言的组成部分的是（）"><a href="#下列选项中不属于结构化查询语言的组成部分的是（）" class="headerlink" title="下列选项中不属于结构化查询语言的组成部分的是（）"></a><strong>下列选项中不属于结构化查询语言的组成部分的是（）</strong></h2><p>A. 数据定义语言</p>
<p>B. 数据处理语言</p>
<p>C. 数据字典</p>
<p>D. 数据库事务</p>
<p>解析：结构化查询语言包含6个部分</p>
<p>一：数据查询语言（DQL：Data Query Language）</p>
<p>二：数据操作语言（DML：Data Manipulation Language）</p>
<p>三：事务处理语言（TPL）</p>
<p>四：数据控制语言（DCL）</p>
<p>五：数据定义语言（DDL）</p>
<p>六：指针控制语言（CCL）</p>
<hr>
<h2 id="Mysql中表student-info-id-name-birth-sex-，字段类型都是varchar，插入：1018-赵六-2003-08-02-男；SQL正确的是（）？"><a href="#Mysql中表student-info-id-name-birth-sex-，字段类型都是varchar，插入：1018-赵六-2003-08-02-男；SQL正确的是（）？" class="headerlink" title="Mysql中表student_info(id,name,birth,sex)，字段类型都是varchar，插入：1018 , 赵六 , 2003-08-02 , 男；SQL正确的是（）？"></a><strong>Mysql中表student_info(id,name,birth,sex)，字段类型都是varchar，插入：1018 , 赵六 , 2003-08-02 , 男；SQL正确的是（）？</strong></h2><p>A. insert overwrite student_info values(‘1018’ , ‘赵六’ , ‘2003-08-02’ , ‘男’);</p>
<p>B. insert into student_info values(1018 , ‘赵六’ , ‘2003-08-02’ , ‘男’);</p>
<p>C. insert into student_info(<code>birth</code>,<code>id</code>,name,<code>sex</code>) values(‘2003-08-02’ ,’1018’ , ‘赵六’ , ‘男’);</p>
<p>D. insert into student_info value(‘1018’ , ‘赵六’ , ‘2003-08-02’ , ‘男’);</p>
<p>解析：</p>
<p>A执行报错，插入时是insert into不是insert overwrite；</p>
<p>B执行报错，id是varchar类型，插入的1018需要加上单引号；</p>
<p>D执行报错，插入时是values不是value；</p>
<p><strong>所以C正确，字段名顺序与字段值顺序一致即可，可以给部分或所有字段名加``。</strong></p>
<hr>
<h2 id="Mysql中表student-table-id-name-birth-sex-，插入如下记录："><a href="#Mysql中表student-table-id-name-birth-sex-，插入如下记录：" class="headerlink" title="Mysql中表student_table(id,name,birth,sex)，插入如下记录："></a><strong>Mysql中表student_table(id,name,birth,sex)，插入如下记录：</strong></h2><p><strong>(‘1003’ , NULL , ‘2002-05-20’ , ‘男’);</strong></p>
<p><strong>(‘1004’ , ‘张三’ , ‘2000-09-06’ , ‘男’);</strong></p>
<p><strong>(‘1005’ , ‘李四’ , ‘2001-12-01’ , ‘女’);</strong></p>
<p><strong>(‘1006’ , NULL , ‘2001-12-02’ , ‘女’);</strong></p>
<p><strong>修改name字段为NULL的记录，是男生时设置name=’男生姓名’，是女生时设置name=’女生姓名’，如下SQL正确的是（）？</strong></p>
<p>A. update student_table set name  = ( case when sex = ‘男’ then ‘男生姓名’ when sex = ‘女’ then ‘女生姓名’ end) if name is null ;</p>
<p>B. update student_table when name is null set name  = ( case when sex = ‘男’ then ‘男生姓名’ when sex = ‘女’ then ‘女生姓名’) </p>
<p>C. update student_table set name  = ( when sex = ‘男’ then ‘男生姓名’ when sex = ‘女’ then ‘女生姓名’) where name is null ;</p>
<p>D. update student_table set name  = ( case when sex = ‘男’ then ‘男生姓名’ when sex = ‘女’ then ‘女生姓名’ end) where name is null ;</p>
<p>解析：</p>
<p>【修改name字段为NULL的记录】需要把where条件写在最后；<br>case when的正确语法是: case when … then … when … then … else … end，case和end不能缺少！<br>所以D正确！<br><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> <span class="keyword">when</span> 判断表达式 <span class="keyword">then</span> 表达式</span><br><span class="line">     <span class="keyword">when</span> 判断表达式 <span class="keyword">then</span> 表达式</span><br><span class="line">     ...</span><br><span class="line">     <span class="keyword">else</span> 表达式</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><br>when子句的判断表达式用于判断某一行数据是否符合某个条件，若符合则返回then子句的表达式，若不符合则继续执行下一个when子句，如果所有的when子句都不符合，则返回else子句的表达式。</p>
<hr>
<h2 id="现在有一个学生表student，需要回收所有机器的nkw用户对学生表student所在数据库user的update和insert权限，则下列语句中能够实现这一功能的语句是（）"><a href="#现在有一个学生表student，需要回收所有机器的nkw用户对学生表student所在数据库user的update和insert权限，则下列语句中能够实现这一功能的语句是（）" class="headerlink" title="现在有一个学生表student，需要回收所有机器的nkw用户对学生表student所在数据库user的update和insert权限，则下列语句中能够实现这一功能的语句是（）"></a><strong>现在有一个学生表student，需要回收所有机器的nkw用户对学生表student所在数据库user的update和insert权限，则下列语句中能够实现这一功能的语句是（）</strong></h2><p>A. revoke update,insert on user.* to ‘nkw’@’%’;</p>
<p>B. revoke update,insert on <em>.</em> to ‘nkw’@’%’;</p>
<p>C. revoke update,insert on user.* from ‘nkw’@’%’;</p>
<p>D. revoke update,insert on <em>.</em> from ‘nkw’@’%’;</p>
<p>解析：</p>
<p>回收表的操作功能语句revoke … on … from。因此答案A、B不正确。由于题目要求是回收所有机器的nkw用户对user表的update和insert权限，而答案D是回收所有数据库的update和insert权限，因此答案D不正确。答案选择C</p>
<p>1.GRANT 赋于权限</p>
<p>常用的系统权限集合有以下三个:</p>
<p>CONNECT(基本的连接),   RESOURCE(程序开发),   DBA(数据库管理)</p>
<p>常用的数据对象权限有以下五个:</p>
<p>ALL   ON   数据对象名,   SELECT   ON   数据对象名,   UPDATE   ON   数据对象名,</p>
<p>DELETE   ON   数据对象名,     INSERT   ON   数据对象名,       ALTER     ON   数据对象名</p>
<p>GRANT   CONNECT,   RESOURCE   TO   用户名;</p>
<p>GRANT   SELECT   ON   表名   TO   用户名;</p>
<p>GRANT   SELECT,   INSERT,   DELETE   ON表名   TO   用户名1,   用户名2;</p>
<p>2.REVOKE   回收权限</p>
<p>REVOKE   CONNECT,   RESOURCE   FROM   用户名;</p>
<p>REVOKE   SELECT   ON   表名   FROM   用户名;</p>
<p>REVOKE   SELECT,   INSERT,   DELETE   ON表名   FROM   用户名1,   用户名2;</p>
<hr>
<h2 id="Mysql中表student-table-id-name-birth-sex-，插入如下记录：-1"><a href="#Mysql中表student-table-id-name-birth-sex-，插入如下记录：-1" class="headerlink" title="Mysql中表student_table(id,name,birth,sex)，插入如下记录："></a><strong>Mysql中表student_table(id,name,birth,sex)，插入如下记录：</strong></h2><p><strong>(‘1001’ , ‘’ , ‘2000-01-01’ , ‘男’);</strong></p>
<p><strong>(‘1002’ , null , ‘2000-12-21’ , ‘男’);</strong></p>
<p><strong>(‘1003’ , NULL , ‘2000-05-20’ , ‘男’);</strong></p>
<p><strong>(‘1004’ , ‘张三’ , ‘2000-08-06’ , ‘男’);</strong></p>
<p><strong>(‘1005’ , ‘李四’ , ‘2001-12-01’ , ‘女’);</strong></p>
<p><strong>查询2001年及之后出生的男生、女生总数，正确的SQL是（）？</strong></p>
<p>A. select sex,count(*) from student_table group by sex  where birth &gt;=’2001’ ;</p>
<p>B. select count(*) from student_table where birth &gt;=’2001’ group by sex ;</p>
<p>C. select sex,count(*) from student_table where birth &gt;=’2001’ group by sex ;</p>
<p>D. select sex,count(*) from student_table group by sex  having birth &gt;=’2001’ ;</p>
<p>解析：<br>题目是求【男生、女生总数】所以需要先写出sex,count(*)，所以B错误；</p>
<p>【2001年及之后出生的】是先卡条件再计数，所以需要用where先卡条件，where是写在group by前面，所以A错误；</p>
<p><strong>having是对计数后的结果做条件判断，所以D语义错误并且执行时也会报错；</strong><br>C是正确选项。</p>
<p>所以说having是对select中指定的列做条件判断，select没有的，having不能使用。所以having啥就要select啥。D选项having中的列在select中没有。并且<strong>题目是先卡条件再计数</strong>，语义也有错误</p>
<hr>
<h2 id="下面哪些字符最可能会导致sql注入"><a href="#下面哪些字符最可能会导致sql注入" class="headerlink" title="下面哪些字符最可能会导致sql注入?"></a><strong>下面哪些字符最可能会导致sql注入?</strong></h2><p>A. ‘（单引号）</p>
<p>B. /</p>
<p>C. “(双引号)</p>
<p>D. $</p>
<p>解析：单引号作为MySQL中的字段值封装方式，最容易被用作注入攻击</p>
<p>SQL注入的关键是单引号的闭合</p>
<hr>
<h2 id="ANY、ALL、Some的用法"><a href="#ANY、ALL、Some的用法" class="headerlink" title="ANY、ALL、Some的用法"></a><strong>ANY、ALL、Some的用法</strong></h2><p>All：对所有数据都满足条件，整个条件才成立；</p>
<p>Any：只要有一条数据满足条件，整个条件成立；</p>
<p>Some的作用和Any一样 .</p>
<hr>
<h2 id="批处理是指包含一条或多条T-SQL语句的语句组，下列选项中，关于批处理的规则描述正确的是（）"><a href="#批处理是指包含一条或多条T-SQL语句的语句组，下列选项中，关于批处理的规则描述正确的是（）" class="headerlink" title="批处理是指包含一条或多条T-SQL语句的语句组，下列选项中，关于批处理的规则描述正确的是（）"></a><strong>批处理是指包含一条或多条T-SQL语句的语句组，下列选项中，关于批处理的规则描述正确的是（）</strong></h2><p>A. 定义一个check约束后，可以在同一个批处理中使用</p>
<p>B. 修改一个表中的字段名后，不可以在同一个批处理中引用这个新字段</p>
<p>C. Create default，Create rule等语句同一个批处理中可以同时提交多个</p>
<p>D. 把规则和默认值绑定到表字段或自定义字段上之后，可以在同一个批处理中使用</p>
<p>解析：</p>
<p>A选项，不能定义一个check约束后，立即在同一个批处理中使用；</p>
<p>C选项，Create default，Create rule，Create trigger，Create procedure，Create view等语句同一个批处理中只能提交一个；</p>
<p>D选项，不能把规则和默认值绑定到表字段或自定义字段上之后，立即在同一个批处理中使用。</p>
<p><strong>某些特殊的SQL指令不能和别的SQL指令共存在一个批处理中。如Create Table命令、Create View命令等，这些指令只能独自存在于一个单独的存储过程中。</strong></p>
<hr>
<h2 id="MYSQL的存储过程和SQL-Server存储过程不同"><a href="#MYSQL的存储过程和SQL-Server存储过程不同" class="headerlink" title="MYSQL的存储过程和SQL Server存储过程不同"></a><strong>MYSQL的存储过程和SQL Server存储过程不同</strong></h2><p>在MySQL的存储变量中，定义变量时可以写成<code>@s_no AS int</code>，这里的AS可以被忽略。而在SQL Server中是不允许出现这个AS的。</p>
<hr>
<h2 id="下列关于视图说法错误的是？"><a href="#下列关于视图说法错误的是？" class="headerlink" title="下列关于视图说法错误的是？"></a><strong>下列关于视图说法错误的是？</strong></h2><p>A. 视图是虚表，它在存储时只存储视图的定义，而没有存储对应的数据</p>
<p>B. 创建视图用CREATE VIEW</p>
<p>C. 修改视图用ALTER VIEW</p>
<p>D. 删除视图用DELETE VIEW</p>
<p>解析：视图是虚表，它在存储时只存储视图的定义，而没有存储对应的数据符合题意，BC也符合视图的语法，而删除视图应该使用DROP VIEW进行，故选D</p>
<hr>
<h2 id="DDL、DML、DQL"><a href="#DDL、DML、DQL" class="headerlink" title="DDL、DML、DQL"></a><strong>DDL、DML、DQL</strong></h2><p>DDL(Data Definition Language)用来定义数据库对象；</p>
<p>DML(Data Manipulation Language)用来对数据库中的表进行增删改操作；</p>
<p>DQL(Data Query Language)用来查询数据库中表的记录； DCL(Data Control Language)用来定义数据库的访问控制权限和安全级别；</p>
<hr>
<h2 id="下列选项关于游标的作用叙述正确的是（）"><a href="#下列选项关于游标的作用叙述正确的是（）" class="headerlink" title="下列选项关于游标的作用叙述正确的是（）"></a><strong>下列选项关于游标的作用叙述正确的是（）</strong></h2><p>A. 不允许定位在结果集的特定行</p>
<p>B. 从结果集的目标位置检索一行或一部分行</p>
<p>C. 不支持对结果集中当前位置的行进行数据修改</p>
<p>D. 为由其他用户对显示在结果集中的数据所做的更改提供不同级别的可见性支持</p>
<p>解析：游标是一种从包括多条数据记录的结果集中每次提取一条记录以便处理的机制，可以看做是查询结果的记录指针。A选项，游标允许定位在结果集的特定行；B选项，从结果集的当前位置检索一行或一部分行；C选项，支持对结果集中当前位置的行进行数据修改。</p>
<hr>
<h2 id="下列关于SQL-Server中使用存储过程的优点叙述不正确的是（）"><a href="#下列关于SQL-Server中使用存储过程的优点叙述不正确的是（）" class="headerlink" title="下列关于SQL Server中使用存储过程的优点叙述不正确的是（）"></a><strong>下列关于SQL Server中使用存储过程的优点叙述不正确的是（）</strong></h2><p>A. 执行速度快</p>
<p>B. 封装复杂操作</p>
<p>C. 允许模块化程序设计</p>
<p>D. 增加网络流量</p>
<p>解析：存储过程包含很多行SQL语句，但在客户机调用存储过程时，网络中只要传送调用存储过程的语句，而无需在网络中发送很多行代码，减少了网络流量，特别是大型、复杂的数据处理，存储过程无需将中间结果集送回客户机，只要发送最终结果。</p>
<hr>
<h2 id="窗口函数"><a href="#窗口函数" class="headerlink" title="窗口函数"></a><strong>窗口函数</strong></h2><p>窗口函数：<br>over()窗口函数的语法结构<strong>及常与over()一起使用的分析函数</strong></p>
<p>1、over()窗口函数的语法结构</p>
<p>2、常与over()一起使用的分析函数</p>
<p>1、over()窗口函数的语法结构</p>
<p>over()函数中包括三个函数：分区partition by 列名、排序order by 列名、指定窗口范围rows between 开始位置 and 结束位置（可用\可以不用）<br>若over()函数中不使用这三个函数，窗口大小是针对查询产生的所有数据，如果指定了分区，窗口大小是针对每个分区的数据。</p>
<p><strong>partition by</strong></p>
<p>partition by可理解为group by 分组。over(partition by 列名)搭配分析函数时，分析函数按照每一组每一组的数据进行计算的。</p>
<p><strong>B、rows between 开始位置 and 结束位置</strong><br>是指定窗口范围，比如第一行到当前行。而这个范围是随着数据变化的。over(rows between 开始位置 and 结束位置)搭配分析函数时，分析函数按照这个范围进行计算的。</p>
<p>2、常与over()一起使用的分析函数：</p>
<p>2.1、聚合类</p>
<p><strong>avg()、sum()、max()、min()</strong></p>
<p>2.1、排名类</p>
<p><strong>rank()</strong> 按照值排序时产生一个自增编号，值相等时会重复，会产生空位（如：1、3、3、6、9）</p>
<p><strong>dense_rank()</strong> 按照值排序时产生一个自增编号，值相等时会重复，不会产生空位（如：1、2、2、3、3、4）</p>
<p><strong>row_number()</strong> 按照值排序时产生一个自增编号，不会重复（如：1、2、3、4、5、6）</p>
<h2 id="临时表"><a href="#临时表" class="headerlink" title="临时表"></a><strong>临时表</strong></h2><p>在查询过程中创建的临时表需要命名，命名就需要使用AS<br>例如：<br><figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> mark <span class="keyword">FROM</span> (</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> mark,<span class="built_in">COUNT</span>(mark) <span class="keyword">AS</span> num</span><br><span class="line"></span><br><span class="line"><span class="keyword">FROM</span> grade</span><br><span class="line"></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> mark</span><br><span class="line"></span><br><span class="line">) <span class="keyword">AS</span> STATISTIC</span><br><span class="line"></span><br><span class="line"><span class="keyword">WHERE</span> num&gt;<span class="number">1</span></span><br></pre></td></tr></table></figure><br>AS不能省略</p>
<hr>
<h2 id="SELECT-COALESCE（NULL，fat，2）AS-name1，COALESCE（pig，test，test）AS-name2-FROM-NAMETABLE；以上SQL执行后name1和name2分别为多少"><a href="#SELECT-COALESCE（NULL，fat，2）AS-name1，COALESCE（pig，test，test）AS-name2-FROM-NAMETABLE；以上SQL执行后name1和name2分别为多少" class="headerlink" title="SELECT COALESCE（NULL，fat，2）AS name1，COALESCE（pig，test，test）AS name2 FROM NAMETABLE；以上SQL执行后name1和name2分别为多少"></a><strong>SELECT COALESCE（NULL，fat，2）AS name1，COALESCE（pig，test，test）AS name2 FROM NAMETABLE；以上SQL执行后name1和name2分别为多少</strong></h2><p>A. NULL，pig</p>
<p>B. 2，test</p>
<p>C. fat，pig</p>
<p>D. null，test</p>
<p>解析：本题主要考察COALESCE函数</p>
<p>COALESCE ( expression,value1,value2……) </p>
<p>COALESCE()函数的第一个参数expression为待检测的表达式，而其后的参数个数不定。</p>
<p>COALESCE()函数将会返回包括expression在内的所有参数中的第一个非空表达式。</p>
<p>如果expression不为空值则返回expression；否则判断value1是否是空值，</p>
<p>如果value1不为空值则返回value1；否则判断value2是否是空值，</p>
<p>如果value2不为空值则返回value2；</p>
<p>……</p>
<p>如果所有的表达式都为空值，则返回NULL。</p>
<hr>
<h2 id="datediff日期函数"><a href="#datediff日期函数" class="headerlink" title="datediff日期函数"></a><strong>datediff日期函数</strong></h2><p>关于日期函数，datediff(日期1，日期2)，得到的结果是日期一与日期二相差的天数，若日期1大于日期2，结果为正，否则结果为负。本题中关键词为前一天，即d2.date应大于d1.date所得结果应为正值。</p>
<hr>
<h2 id="Mysql中表student-table-id-name-birth-sex-，插入如下记录：-2"><a href="#Mysql中表student-table-id-name-birth-sex-，插入如下记录：-2" class="headerlink" title="Mysql中表student_table(id,name,birth,sex)，插入如下记录："></a><strong>Mysql中表student_table(id,name,birth,sex)，插入如下记录：</strong></h2><p><strong>(‘1001’ , ‘’ , ‘2000-01-01’ , ‘男’);</strong><br><strong>(‘1002’ , null , ‘2000-12-21’ , ‘男’);</strong><br><strong>(‘1003’ , NULL , ‘2000-05-20’ , ‘男’);</strong><br><strong>(‘1004’ , ‘张三’ , ‘2000-08-06’ , ‘男’);</strong><br><strong>(‘1005’ , ‘李四’ , ‘2001-12-01’ , ‘女’);</strong><br><strong>执行 select count(name) from student_table 的结果是（）？</strong></p>
<p>A. 5</p>
<p>B. 4</p>
<p>C. 3</p>
<p>D. 2</p>
<p>解析：</p>
<p>count(name) 时不包括null值，所以结果是3；</p>
<p>插入时null与NULL的意思一样都是NULL。</p>
<p>‘’ 这种只是没有实际的值，不是null（空），也是可以查询出来的</p>
<p>count()函数有两种使用方式：1、使用count(*)对表中的行数进行计数，此种用法不忽略null值；2、使用count(column)对特定列中具有值的行进行计数，此种用法忽略null值。</p>
<hr>
<h2 id="关于返回受上一个SQL语句影响的行数，下列SQL语句正确的是（）"><a href="#关于返回受上一个SQL语句影响的行数，下列SQL语句正确的是（）" class="headerlink" title="关于返回受上一个SQL语句影响的行数，下列SQL语句正确的是（）"></a><strong>关于返回受上一个SQL语句影响的行数，下列SQL语句正确的是（）</strong></h2><p>A. SELECT @@ERROR</p>
<p>B. SELECT @@IDENTITY</p>
<p>C. SELECT @@ROWCOUNT</p>
<p>D. SELECT @@MAX_CONNECTIONS</p>
<p>解析：</p>
<p>@@ERROR：返回执行的上一个 Transact-SQLTransact-SQL 语句的错误号。</p>
<p>@@IDENTITY：返回自增id。</p>
<p>@@ROWCOUNT：返回受上一个SQL语句影响的行数。</p>
<p>@@MAX_CONNECTIONS：返回最大用户连接数。</p>
<hr>
<h2 id="Rank-函数"><a href="#Rank-函数" class="headerlink" title="Rank()函数"></a><strong>Rank()函数</strong></h2><p>Rank()函数：为结果集分区中每一行分配一个排名，行等级由一加上前面的等级指定。</p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RANK（） <span class="keyword">OVER</span>（</span><br><span class="line">        <span class="keyword">PARTITION</span> <span class="keyword">BY</span>　表达式      ##将结果集划分为分区</span><br><span class="line">        <span class="keyword">ORDER</span> <span class="keyword">BY</span>　表达式　［<span class="keyword">ASC</span>｜<span class="keyword">DESC</span>］ ##对分区内的进行排序</span><br><span class="line">）</span><br></pre></td></tr></table></figure>
<p>在这个语法中：</p>
<p>首先，PARTITION BY子句将结果集划分为分区。RANK()功能在分区内执行，并在跨越分区边界时重新初始化。</p>
<p>其次，ORDER BY子句按一个或多个列或表达式对分区内的行进行排序。</p>
<p>参考：<a target="_blank" rel="noopener" href="https://www.begtut.com/mysql/mysql-rank-function.html">https://www.begtut.com/mysql/mysql-rank-function.html</a></p>
<h2 id="字符串索引"><a href="#字符串索引" class="headerlink" title="字符串索引"></a><strong>字符串索引</strong></h2><h3 id="查询显示雇员的姓名和姓名中是否含有字母A的信息，满足如下条件"><a href="#查询显示雇员的姓名和姓名中是否含有字母A的信息，满足如下条件" class="headerlink" title="查询显示雇员的姓名和姓名中是否含有字母A的信息，满足如下条件"></a><strong>查询显示雇员的姓名和姓名中是否含有字母A的信息，满足如下条件</strong></h3><p><strong>如果字符A在姓名的首位，则显示’字符A在首位’</strong><br><strong>如果字符A在姓名的末位，则显示’字符A在末位’</strong><br><strong>如果字符A在姓名中不存在，则显示’没有字符A’</strong><br><strong>其他情况显示’字符A在中间’</strong><br><strong>下列操作语句正确的是：（）</strong></p>
<p>A.<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ename, <span class="function"><span class="keyword">case</span>  <span class="title">charindex</span>(<span class="params">‘A‘,ename</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘字符A在首位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> <span class="title">len</span>(<span class="params">ename</span>) then</span></span><br><span class="line"><span class="function">‘字符A在末位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘没有字符A‘</span></span><br><span class="line"><span class="function"><span class="keyword">else</span> ‘字符A在中间‘</span></span><br><span class="line"><span class="function">end 名称类别   <span class="keyword">from</span> emp</span>;</span><br></pre></td></tr></table></figure></p>
<p>B.<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ename, <span class="function"><span class="keyword">case</span>  <span class="title">charindex</span>(<span class="params">‘A‘,ename</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 1 then ‘字符A在首位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> <span class="title">len</span>(<span class="params">ename</span>)+1 then</span></span><br><span class="line"><span class="function">‘字符A在末位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘没有字符A‘</span></span><br><span class="line"><span class="function"><span class="keyword">else</span> ‘字符A在中间‘</span></span><br><span class="line"><span class="function">end 名称类别   <span class="keyword">from</span> emp</span>;</span><br></pre></td></tr></table></figure></p>
<p>C.<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ename, <span class="function"><span class="keyword">case</span>  <span class="title">charindex</span>(<span class="params">‘A‘,ename</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 1 then ‘字符A在首位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> <span class="title">len</span>(<span class="params">ename</span>) then</span></span><br><span class="line"><span class="function">‘字符A在末位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘没有字符A‘</span></span><br><span class="line"><span class="function"><span class="keyword">else</span> ‘字符A在中间‘</span></span><br><span class="line"><span class="function">end 名称类别   <span class="keyword">from</span> emp</span>;</span><br></pre></td></tr></table></figure></p>
<p>D.<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> ename, <span class="function"><span class="keyword">case</span>  <span class="title">charindex</span>(<span class="params">‘A‘,ename</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘字符A在首位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> <span class="title">len</span>(<span class="params">ename</span>)+1 then</span></span><br><span class="line"><span class="function">‘字符A在末位‘</span></span><br><span class="line"><span class="function"><span class="keyword">when</span> 0 then ‘没有字符A‘</span></span><br><span class="line"><span class="function"><span class="keyword">else</span> ‘字符A在中间‘</span></span><br><span class="line"><span class="function">end 名称类别   <span class="keyword">from</span> emp</span>;</span><br></pre></td></tr></table></figure></p>
<p>解析：</p>
<p>数据库中,字符串的索引,开始于1。</p>
<p>charindex（用于判断一个字符串中是否包含另一个字符串）</p>
<p>基本语法如下：<br><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">CHARINDEX ( expressionToFind , expressionToSearch [ , start<span class="emphasis">_location ] )</span></span><br><span class="line"><span class="emphasis">    expressionToFind ：目标字符串，就是想要找到的字符串，最大长度为8000 。</span></span><br><span class="line"><span class="emphasis">expressionToSearch ：用于被查找的字符串。</span></span><br><span class="line"><span class="emphasis">    start_</span>location：开始查找的位置，为空时默认从第一位开始查找。</span><br></pre></td></tr></table></figure></p>
<hr>
<h2 id="STUFF函数"><a href="#STUFF函数" class="headerlink" title="STUFF函数"></a><strong>STUFF函数</strong></h2><p>STUFF(原字符, 开始位置, 删除长度, 插入字符)</p>
<p>从指定的起点处开始删除指定长度的字符，并在此处插入另一组字符</p>
<h2 id="下列选项中关于数据库事务的特性描述正确的是（）"><a href="#下列选项中关于数据库事务的特性描述正确的是（）" class="headerlink" title="下列选项中关于数据库事务的特性描述正确的是（）"></a><strong>下列选项中关于数据库事务的特性描述正确的是（）</strong></h2><p>A. 事务允许继续分割</p>
<p>B. 多个事务在执行事务前后对同一个数据读取的结果是不同的</p>
<p>C. 一个事务对数据库中数据的改变是暂时的</p>
<p>D. 并发访问数据库时，各并发事务之间数据库是独立的</p>
<p>解析：</p>
<p>此题考查数据库事务的原子性、一致性、隔离性和持久性。</p>
<p>A选项，事务是最小的执行单位，不允许分割；</p>
<p>B选项，执行事务前后，数据保持一致，对同一数据读取的结果相同；</p>
<p>C选项，一个事务被提交后对数据库中数据的改变是持久的。</p>
<p>事务隔离性存在隔离级别。理论存在4个级别。读未提交，读已提交。可重复读。序列化（串行化）。</p>
<p>读未提交：会导致脏读、不可重复读取、幻读问题。脏读（即一个事务读取了另一个事务未提交的数据）。</p>
<p>不可重复读（一个事务还未提交。读取了另一个事务使用update和delete语句后。提交的数据。产生了不同的结果集，这种现象称为。不可以重复读取。）。</p>
<p>幻读（同一个查询还未提交。读取了另一个事务使用insert语句后。且提交后。产生了不同的结果集。这种现象称为。幻读）。</p>
<p>读已提交：会导致 不可重复读和幻读问题。解决了脏读。</p>
<p>可重复读取：解决了上述，三个问题不加锁。msyql innodb(默认第三种隔离级别)</p>
<p>序列化：也解决了上述，三个问题。但是使用了锁的方式。</p>
<h2 id="事务的特点-ACID"><a href="#事务的特点-ACID" class="headerlink" title="事务的特点(ACID)"></a><strong>事务的特点(ACID)</strong></h2><ul>
<li><p>A 原子性：一个事务是不可再分割的整体，要么都执行要么都不执行</p>
</li>
<li><p>C 一致性：一个事务可以使数据从一个一致状态切换到另外一个一致的状态</p>
</li>
<li><p>I 隔离性：一个事务不受其他事务的干扰，多个事务互相隔离的</p>
</li>
<li><p>D 持久性：一个事务一旦提交了，则永久的持久化到本地</p>
</li>
</ul>
<hr>
<h2 id="下列选项关于函数的描述正确的是（）"><a href="#下列选项关于函数的描述正确的是（）" class="headerlink" title="下列选项关于函数的描述正确的是（）"></a><strong>下列选项关于函数的描述正确的是（）</strong></h2><p>A. 用户定义函数可以用于执行修改数据库状态的操作</p>
<p>B. 用户定义函数属于数据库，可以在多个数据库下调用</p>
<p>C. 与系统函数一样，用户定义函数可以从查询中调用</p>
<p>D. 标量函数不可使用EXECUTE语句执行</p>
<p>解析：A选项，用户定义函数不用于执行修改数据库状态的操作；B选项，用户定义函数属于数据库，只能在该数据库下调用；D选项，标量函数和存储过程一样，可以使用EXECUTE语句执行。</p>
<hr>
<h2 id="游标的INSENSITIVE参数"><a href="#游标的INSENSITIVE参数" class="headerlink" title="游标的INSENSITIVE参数"></a><strong>游标的INSENSITIVE参数</strong></h2><p>INSENSITIVE</p>
<p>表明MS SQL SERVER 会将游标定义所选取出来的数据记录存放在一临时表内（建立在tempdb 数据库下）。对该游标的读取操作皆由临时表来应答。因此，对基本表的修改并不影响游标提取的数据，即游标不会随着基本表内容的改变而改变，同时也无法通过<br>游标来更新基本表。如果不使用该保留字，那么对基本表的更新、删除都会反映到游标中。</p>
<p>当遇到以下情况发生时，游标将自动设定INSENSITIVE 选项。</p>
<p>在SELECT 语句中使用DISTINCT、 GROUP BY、 HAVING UNION 语句；</p>
<p>使用OUTER JOIN；</p>
<p>所选取的任意表没有索引；</p>
<p>将实数值当作选取的列</p>
<h2 id="下面有关sql-语句中-delete、truncate的说法正确的是？（）"><a href="#下面有关sql-语句中-delete、truncate的说法正确的是？（）" class="headerlink" title="下面有关sql 语句中 delete、truncate的说法正确的是？（）"></a><strong>下面有关sql 语句中 delete、truncate的说法正确的是？（）</strong></h2><p>A. 论清理表数据的速度，truncate一般比delete更快</p>
<p>B. truncate命令可以用来删除部分数据。</p>
<p>C. truncate只删除表的数据不删除表的结构</p>
<p>D. delete能够回收高水位(自增ID值)</p>
<p>解析：</p>
<ol>
<li><p>处理效率：drop&gt;trustcate&gt;delete</p>
</li>
<li><p>删除范围：drop删除整个表（结构和数据一起删除）；trustcate删除全部记录，但不删除表结构；delete只删除数据</p>
</li>
<li><p>高水位线：delete不影响自增ID值，高水线保持原位置不动；trustcate会将高水线复位，自增ID变为1。</p>
</li>
</ol>
<h2 id="在SQL中语法规范中，having子句的使用下面描述正确的是：（-）"><a href="#在SQL中语法规范中，having子句的使用下面描述正确的是：（-）" class="headerlink" title="在SQL中语法规范中，having子句的使用下面描述正确的是：（    ）"></a><strong>在SQL中语法规范中，having子句的使用下面描述正确的是：（    ）</strong></h2><p>A. having子句即可包含聚合函数作用的字段也可包括普通的标量字段</p>
<p>B. 使用having的同时不能使用where子句</p>
<p>C. having子句必须于group by 子句同时使用，不能单独使用</p>
<p>D. 使用having子句的作用是限定分组条件</p>
<p>E. Having子句和where子句是等同的</p>
<p>F. 如果select语句中没有聚合函数的使用，就不能使用having子句</p>
<p>解析：</p>
<p>having是在分组后过滤，where在分组前过滤，不冲突，可以同时使用，BE错；</p>
<p>having是用来过滤的，group by是限定分组，D错；</p>
<p><strong>select语句中没有聚合函数的使用时也可以用having，F错</strong></p>
<hr>
<h2 id="lt-gt-符号"><a href="#lt-gt-符号" class="headerlink" title="&lt;&gt;符号"></a><strong>&lt;&gt;符号</strong></h2><p>select * from student_table  where id  in (<br>select stu_id from score_table where subject_name &lt;&gt; ‘英语’);</p>
<p>其中&lt;&gt;符号表示subject_name不等于英语条件。</p>
<h2 id="ROUND-函数和TRUNCATE-函数"><a href="#ROUND-函数和TRUNCATE-函数" class="headerlink" title="ROUND()函数和TRUNCATE()函数"></a><strong>ROUND()函数和TRUNCATE()函数</strong></h2><p>TRUNCATE函数将expr按照int_expr长度在小数点后按照位数直接进行截取。</p>
<p>实例：</p>
<p>SELECT TRUNCATE(200.12567,4);</p>
<p>输出结果：200.1256</p>
<p>ROUND(expr, int_expr)用法</p>
<p>ROUND函数将expr按照int_expr长度在小数点后按照位数进行四舍五入截取。</p>
<p>实例：</p>
<p>SELECT ROUND(200.12567,4);</p>
<p>输出结果：200.1567</p>
<hr>
<h2 id="union用法"><a href="#union用法" class="headerlink" title="union用法"></a><strong>union用法</strong></h2><p>使用where<br><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span></span><br><span class="line"><span class="type">name</span>,population,area</span><br><span class="line"><span class="keyword">from</span> World</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">area&gt;<span class="number">3000000</span></span><br><span class="line"><span class="keyword">or</span> </span><br><span class="line">population&gt;<span class="number">25000000</span></span><br><span class="line">;</span><br></pre></td></tr></table></figure><br>使用union<br><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span></span><br><span class="line"><span class="type">name</span>,population,area</span><br><span class="line"><span class="keyword">from</span> World</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">area&gt;<span class="number">3000000</span></span><br><span class="line"><span class="keyword">union</span></span><br><span class="line"><span class="keyword">select</span></span><br><span class="line"><span class="type">name</span>,population,area</span><br><span class="line"><span class="keyword">from</span> World</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">population&gt;<span class="number">25000000</span></span><br><span class="line">;</span><br></pre></td></tr></table></figure></p>
<p>使用 union 的速度比where要快</p>
<p>使用union的注意规则：</p>
<p>1.UNION必须由两条以上的SELECT语句组成，语句之间用关键字UNION分割。</p>
<p>2.UNION中的每个查询必须包含相同的列、表达式或聚集函数（各个列不需要以相同的次序列出）。</p>
<p>3.列数据类型必须兼容：类型不必完全相同，但必须是DBMS可以隐含地转换的类型。</p>
<p>4.如果取出来的数据不需要去重，使用UNION ALL。</p>
<hr>
<h2 id="Charindex-、SubString-、Stuff-、Soundex-函数"><a href="#Charindex-、SubString-、Stuff-、Soundex-函数" class="headerlink" title="Charindex()、SubString()、Stuff()、Soundex()函数"></a><strong>Charindex()、SubString()、Stuff()、Soundex()函数</strong></h2><p>Charindex()返回字符或字符串在另一个字符的起始位置CHARINDEX(查找字符，被查字符，开始位置)</p>
<p>SubString()截取字符串中的一部分字符。SUBSTRING（字符串，开始位置，截取长度）</p>
<p>Stuff()删除指定长度的字符，并在指定的起点处插入另一组字符。STUFF(列名,开始位置,长度,替代字符串)</p>
<p>Soundex()返回表示字符串声音的一串字符</p>
<h2 id="RENAME、MODIFY、ALTER、CHANGE的用法"><a href="#RENAME、MODIFY、ALTER、CHANGE的用法" class="headerlink" title="RENAME、MODIFY、ALTER、CHANGE的用法"></a><strong>RENAME、MODIFY、ALTER、CHANGE的用法</strong></h2><p>RENAME用于表的重命名：RENAME  <NAME> 或  RENAME  TO  <NAME></NAME></NAME></p>
<p>MODIFY用于字段类型的修改：MODIFY  COLUMN   &lt;列名&gt;  &lt;类型&gt;</p>
<p>ALTER用于对字段类型、默认值的修改：ALTER  COLUMN  &lt;列名&gt;  &lt;类型&gt;    SET  DE<em>*</em>T  &lt;默认值&gt;</p>
<p>CHANGE用于对列名及类型的修改：CHANGE  COLUMN  &lt;旧列名&gt;  &lt;新列名&gt;  &lt;类型&gt;</p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> employee <span class="keyword">RENAME</span>  employee_info;</span><br></pre></td></tr></table></figure>
<h2 id="Mysql-版本8-0-25-不支持full-join"><a href="#Mysql-版本8-0-25-不支持full-join" class="headerlink" title="Mysql(版本8.0.25)不支持full join"></a><strong>Mysql(版本8.0.25)不支持full join</strong></h2><h2 id="在SQL中如何删除触发器？"><a href="#在SQL中如何删除触发器？" class="headerlink" title="在SQL中如何删除触发器？"></a><strong>在SQL中如何删除触发器？</strong></h2><figure class="highlight n1ql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DROP</span> <span class="keyword">TRIGGER</span> 触发器名称</span><br></pre></td></tr></table></figure>
<h2 id="已知某期刊信息库中有作家信息表author（作者编号aid，作者姓名aname，作者笔名ausername，作者邮箱aemail，备注remarks），稿件表manuscript（稿件编号mid，标题mtitle，作者编号aid，交稿时间mtime）现需要设置外键作者编号，下列语句正确的是（B）"><a href="#已知某期刊信息库中有作家信息表author（作者编号aid，作者姓名aname，作者笔名ausername，作者邮箱aemail，备注remarks），稿件表manuscript（稿件编号mid，标题mtitle，作者编号aid，交稿时间mtime）现需要设置外键作者编号，下列语句正确的是（B）" class="headerlink" title="已知某期刊信息库中有作家信息表author（作者编号aid，作者姓名aname，作者笔名ausername，作者邮箱aemail，备注remarks），稿件表manuscript（稿件编号mid，标题mtitle，作者编号aid，交稿时间mtime）现需要设置外键作者编号，下列语句正确的是（B）"></a><strong>已知某期刊信息库中有作家信息表author（作者编号aid，作者姓名aname，作者笔名ausername，作者邮箱aemail，备注remarks），稿件表manuscript（稿件编号mid，标题mtitle，作者编号aid，交稿时间mtime）现需要设置外键作者编号，下列语句正确的是（B）</strong></h2><p>A. </p>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> manuscript</span><br><span class="line"><span class="keyword">ADD</span> <span class="keyword">CONSTRAINT</span> FK_aid</span><br><span class="line"><span class="keyword">FOREIGN KEY</span>  (aid) <span class="keyword">REFERENCES</span> author (aid)</span><br></pre></td></tr></table></figure>
<p>B.<br><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> manuscript</span><br><span class="line"><span class="keyword">ADD</span> <span class="keyword">CONSTRAINT</span> FK_aid</span><br><span class="line"><span class="keyword">FOREIGN KEY</span>  (manuscript.aid) <span class="keyword">REFERENCES</span> author (aid)</span><br></pre></td></tr></table></figure></p>
<p>C.<br><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> manuscript</span><br><span class="line"><span class="keyword">ADD</span> <span class="keyword">FOREIGN</span> <span class="keyword">KEY</span>  (manuscript.aid)</span><br><span class="line"><span class="keyword">REFERENCES</span> author (aid)</span><br></pre></td></tr></table></figure></p>
<p>D.<br><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> manuscript</span><br><span class="line"><span class="keyword">ADD</span> <span class="keyword">FOREIGN</span> <span class="keyword">KEY</span>  (aid)</span><br><span class="line"><span class="keyword">REFERENCES</span> author (aid)</span><br></pre></td></tr></table></figure></p>
<h2 id="关于MySQL常见索引的描述正确是（C）"><a href="#关于MySQL常见索引的描述正确是（C）" class="headerlink" title="关于MySQL常见索引的描述正确是（C）"></a><strong>关于MySQL常见索引的描述正确是（C）</strong></h2><p>A. 创建UNIQUE索引，索引列的值必须唯一，不允许有空值</p>
<p>B. 一个表中可以创建多个全文索引</p>
<p>C. 为提高效率可建立组合索引，遵循“最左前缀”原则</p>
<p>D. 使用非聚集索引需要将物理数据页中的数据按列重新排序</p>
<p>解析：</p>
<p>A选项，唯一索引不允许两行具有相同的索引值，包括NULL值，允许有空值；</p>
<p>B选项，每个表只允许有一个全文索引；</p>
<p>D选项，非聚集索引具有完全独立于数据行的结构，所以不需要将物理数据页中的数据按列重新排序。</p>
<h2 id="数据库备份"><a href="#数据库备份" class="headerlink" title="数据库备份"></a><strong>数据库备份</strong></h2><p>1、完全备份</p>
<p>这可能是大多数人常用的方式，它可以备份整个数据库，包含用户表、系统表、索引、视图和存储过程等所有数据库对象。但是呢，它也需要花费更多的时间和空间，所以，一般推荐一周做一次完全备份。</p>
<p>2、事务日志备份</p>
<p>事务日志是一个单独的文件，它记录数据库的改变，备份的时候只需要复制自上次备份以来对数据库所做的改变，所以只需要很少的时间。为了使数据库具有鲁棒性，推荐每小时甚至更频繁的备份事务日志。</p>
<p>3、差异备份</p>
<p>也叫增量备份。它是只备份数据库一部分的另一种方法，它不使用事务日志，相反，它使用整个数据库的一种新映象。它比最初的完全备份小，因为它只包含自上次完全备份以来所改变的数据库。它的优点是存储和恢复速度快。推荐每天做一次差异备份。</p>
<p>4、文件备份</p>
<p>数据库可以由硬盘上的许多文件构成。如果这个数据库非常大，并且一个晚上也不能将它备份完，那么可以使用文件备份每晚备份数据库的一部分。由于一般情况下数据库不会大到必须使用多个文件存储，所以这种备份不是很常用</p>
<h2 id="if函数"><a href="#if函数" class="headerlink" title="if函数"></a><strong>if函数</strong></h2><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">select sno,</span><br><span class="line">sum(<span class="keyword">if</span>(<span class="keyword">class</span>=&#x27;<span class="symbol">english</span>&#x27;,<span class="symbol">score,<span class="symbol">0</span></span>)) <span class="symbol">as</span> <span class="symbol">english,</span></span><br><span class="line"><span class="symbol"><span class="symbol">sum</span></span>( <span class="symbol">if</span>(<span class="symbol">class</span>=&#x27;<span class="symbol">math</span>&#x27;,<span class="symbol">score,<span class="symbol">0</span></span>) ) <span class="symbol">as</span> <span class="symbol">math</span></span><br><span class="line"><span class="symbol">from</span> <span class="symbol">sc</span></span><br><span class="line"><span class="symbol">where</span> <span class="symbol">class</span> <span class="symbol">in</span>(&#x27;<span class="symbol">english</span>&#x27;,&#x27;<span class="symbol">math</span>&#x27;)</span><br><span class="line"><span class="symbol">group</span> <span class="symbol">by</span> <span class="symbol">sno</span></span><br></pre></td></tr></table></figure>
<h2 id="视图是一种数据库对象，是从数据库的表或其他视图中导出的基表？"><a href="#视图是一种数据库对象，是从数据库的表或其他视图中导出的基表？" class="headerlink" title="视图是一种数据库对象，是从数据库的表或其他视图中导出的基表？"></a><strong>视图是一种数据库对象，是从数据库的表或其他视图中导出的基表？</strong></h2><p>视图是虚拟表，是一个依赖于别的表上的寄生表，其本身不存储数据，对视图的操作最终都会转化为对基本表的操作。所以说视图不能是从其他视图导出的</p>
<h2 id="SUM-函数的加一操作和COUNT-函数计数操作"><a href="#SUM-函数的加一操作和COUNT-函数计数操作" class="headerlink" title="SUM()函数的加一操作和COUNT()函数计数操作"></a><strong>SUM()函数的加一操作和COUNT()函数计数操作</strong></h2><p>使用SUM函数，满足条件就计1，不满足就计0</p>
<p>使用COUNT函数，是计的返回的条数，不管返回的是0、1还是2，都会计1</p>
<h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a><strong>索引</strong></h2><p>索引滥用会降低更新表的速度，更新表时，MySQL不仅要更新数据，保存数据，还要更新索引，保存索引，索引会占用磁盘空间。</p>
<p>所以索引虽然提高了查询速度，但不会提高更新表的速度，甚至还会减慢表的速度</p>
<p>数据库索引采用B+树是因为B+树在提高了磁盘IO性能的同时解决了元素遍历效率低下的问题</p>
<p>如果WHERE子句中使用了索引，那么ORDER BY子句中不会使用索引</p>
<h2 id="事务和并发控制"><a href="#事务和并发控制" class="headerlink" title="事务和并发控制"></a><strong>事务和并发控制</strong></h2><p>并发控制机制的好坏是衡量一个数据库管理系统性能的重要标志之一</p>
<p>可串行性是判断并发事务是否正确的准则</p>
<p>事务的隔离级别越高，并发能力也就越低</p>
<p>SQL Server中每一条select、insert、update、delete语句都是隐形事务的一部分，显性事务用BEGIN TRANSACTION明确指定事务。</p>
<h2 id="视图"><a href="#视图" class="headerlink" title="视图"></a><strong>视图</strong></h2><p>视图可以被嵌套，一个视图中可以嵌套另一个视图</p>
<h2 id="假设牛客网上有一份绝密文件存于某台机器的secretData数据库中的某个表里面，现在出于数据安全的考虑，对于新创建的用户都只能拥有该机器的登录权限，而不能拥有数据库的其他权限，那么新创建nkw用户满足这一要求的语句是（）"><a href="#假设牛客网上有一份绝密文件存于某台机器的secretData数据库中的某个表里面，现在出于数据安全的考虑，对于新创建的用户都只能拥有该机器的登录权限，而不能拥有数据库的其他权限，那么新创建nkw用户满足这一要求的语句是（）" class="headerlink" title="假设牛客网上有一份绝密文件存于某台机器的secretData数据库中的某个表里面，现在出于数据安全的考虑，对于新创建的用户都只能拥有该机器的登录权限，而不能拥有数据库的其他权限，那么新创建nkw用户满足这一要求的语句是（）"></a><strong>假设牛客网上有一份绝密文件存于某台机器的secretData数据库中的某个表里面，现在出于数据安全的考虑，对于新创建的用户都只能拥有该机器的登录权限，而不能拥有数据库的其他权限，那么新创建nkw用户满足这一要求的语句是（）</strong></h2><p>A. grant usage on <em>.</em> with ‘nkw’@’%’;</p>
<p>B. grant usage on secretData.* to ‘nkw’@’%’;</p>
<p>C. grant usage on secretData.* with ‘nkw’@’%’;</p>
<p>D. grant usage on <em>.</em> to ‘nkw’@’%’;</p>
<p>解析：grant usage on <em>.</em> to ‘nkw’@’%’;授权的用户对该机器下的下的所有数据库下的所有表有登录权限</p>
<p>给数据库赋予登录权限的语句是grant usage on … to。因此答案A、C都不正确。题目中只说secretData数据库中的某个表有绝密文件，但是对新创建的用户可拥有登录权限，而没有其他权限。并没有说只对secretData数据库，因此题目引入该数据库只是一个干扰信息，所以答案B错误，因此答案选择D</p>
<h2 id="维护参照完整性约束的策略"><a href="#维护参照完整性约束的策略" class="headerlink" title="维护参照完整性约束的策略"></a><strong>维护参照完整性约束的策略</strong></h2><p>对于任何违反了参照完整性约束的数据更新，系统一概拒绝执行</p>
<p>当删除被参照表的一个元组造成了与参照表的不一致，则删除参照表中的所有造成不一致的元组</p>
<p>当删除或修改被参照表的一个元组造成了不一致，则将参照表中的所有造成和不一致的元组的对应属性设置为空值</p>
<p>参照完整性共分四种模式：不执行操作、级联、置空、设置默认值。</p>
<p>B选项对应级联操作，即主键列(被参照表)删除同时外键列(参照列)对应列也被删除；</p>
<p>C选项也对应级联操作，但应为修改外键列(参照表)中的数据；</p>
<p>D选项对应置空，即主键列数据删除或者修改时外键列对应数据被置为空值；</p>
<p>设置默认值模式为主键列的修改和删除使对应外键列数据被置为设定的默认值</p>
<h2 id="关于-lt-gt-也会将null值排除掉的问题"><a href="#关于-lt-gt-也会将null值排除掉的问题" class="headerlink" title="关于&lt;&gt;也会将null值排除掉的问题"></a><strong>关于&lt;&gt;也会将null值排除掉的问题</strong></h2><p>where name &lt;&gt; ‘张三’ 会把记录中name=NULL的值对应的记录排除掉，不止排除掉不等于张三的值。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" class="post-title-link" itemprop="url">多线程</a>
        </h2>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-04-10 09:21:12" itemprop="dateCreated datePublished" datetime="2022-04-10T09:21:12+08:00">2022-04-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-13 08:40:26" itemprop="dateModified" datetime="2022-04-13T08:40:26+08:00">2022-04-13</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <h1 id="线程的常用方法"><a href="#线程的常用方法" class="headerlink" title="线程的常用方法"></a><strong>线程的常用方法</strong></h1><p><code>currentThread()方法</code></p>
<p>该方法可以获取当前线程，该方法的返回值是在代码实际运行时候的线程对象。</p>
<p><code>setName()/getName()方法</code></p>
<p>设置线程名称/返回线程名称</p>
<p><code>isAlive()方法</code></p>
<p>布尔类型，判断当前线程是否处于活动状态，活动状态就是线程已启动并且尚未终止</p>
<p><code>sleep()方法</code></p>
<p>Thread.sleep(millis); 让当前线程休眠指定的毫秒数</p>
<p><code>getId()方法</code></p>
<p>可以获得线程的唯一标识，某个编号的线程运行结束后,该编号可能被后续创建的线程使用。重启的 JVM 后,同一个线程的编号可能不一样</p>
<p><code>yield()方法</code></p>
<p>作用是放弃当前的 CPU 资源</p>
<p><code>setPriority()</code></p>
<p>thread.setPriority( num );设置线程的优先级,java 线程的优先级取值范围是 1 ~ 10 , 如果超出这个范围会抛出 异常 IllegalArgumentException。在操作系统中,优先级较高的线程获得 CPU 的资源越多；线程优先级本质上是只是给线程调度器一个提示信息,以便于调 度器决定先调度哪些线程. 注意不能保证优先级高的线程先运行.</p>
<p><code>interrupt()</code></p>
<p>中断线程。注意调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程。是否中断该线程还需要当前线程自行判断。</p>
<p><code>setDaemon()</code></p>
<p>设置当前线程为守护线程</p>
<h1 id="多线程编程的优势与存在的风险"><a href="#多线程编程的优势与存在的风险" class="headerlink" title="多线程编程的优势与存在的风险"></a><strong>多线程编程的优势与存在的风险</strong></h1><p>多线程编程具有以下优势:</p>
<ol>
<li><p>提高系统的吞吐率(Throughout)：多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作</p>
</li>
<li><p>提高响应性(Responsiveness)：Web 服务器会采用一些专门的线 程负责用户的请求处理,缩短了用户的等待时间</p>
</li>
<li><p>充分利用多核(Multicore)处理器资源：通过多线程可以充分的 利用 CPU 资源</p>
</li>
</ol>
<p>多线程编程存在的问题与风险：</p>
<ol>
<li><p>线程安全(Thread safe)问题：多线程共享数据时,如果没有采取 正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数 据(过期的数据), 如丢失数据更新</p>
</li>
<li><p>线程活性(thread liveness)问题：由于程序自身的缺陷或者由资 源稀缺性导致线程一直处于非 RUNNABLE 状态,这就是线程活性问题, 常见的活性故障有以下几种</p>
<ul>
<li><p>死锁(Deadlock)。 类似鹬蚌相争</p>
</li>
<li><p>锁死(Lockout), 类似于睡美人故事中王子挂了</p>
</li>
<li><p>活锁(Livelock)。类似于小猫咬自己尾巴</p>
</li>
<li><p>饥饿(Starvation)。类似于健壮的雏鸟总是从母鸟嘴中抢到食物.</p>
</li>
</ul>
</li>
<li><p>上下文切换(Context Switch)：处理器从执行一个线程切换到执 行另外一个线程</p>
</li>
<li><p>可靠性：可能会由一个线程导致JVM意外终止,其他的线程也无法执行.</p>
</li>
</ol>
<h1 id="线程安全问题"><a href="#线程安全问题" class="headerlink" title="线程安全问题"></a><strong>线程安全问题</strong></h1><p><strong>非线程安全主要是指多个线程对同一个对象的实例变量进行操作时,会出现值被更改,值不同步的情况。线程安全问题表现为三个方面: 原子性,可见性和有序性</strong></p>
<h2 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a><strong>原子性</strong></h2><p>原子(Atomic)就是不可分割的意思. 原子操作的不可分割有两层含义:</p>
<ol>
<li><p>访问(读,写)某个共享变量的操作从其他线程来看,该操作要 么已经执行完毕,要么尚未发生, 即其他线程年示到当前操作的中间结果</p>
</li>
<li><p>访问同一组共享变量的原子操作是不能够交错的</p>
</li>
</ol>
<h2 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a><strong>可见性</strong></h2><p>如果一个线程对共享变量更新后, 后续访问该变量的其他线程可 以读到更新的结果, 称这个线程对共享变量的更新对其他线程可见, 否则称这个线程对共享变量的更新对其他线程不可见</p>
<p>多线程程序因为可见性问题可能会导致其他线程读取到了旧数据(脏数据)</p>
<h2 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a><strong>有序性</strong></h2><p>有序性(Ordering)是指在什么情况下一个处理器上运行的一个线 程所执行的 内存访问操作在另外一个处理器运行的其他线程看来是乱序的(Out of Order)</p>
<h3 id="重排序"><a href="#重排序" class="headerlink" title="重排序"></a><strong>重排序</strong></h3><p>在多核处理器的环境下,编写的顺序结构,这种操作执行的顺序可能是没有保障的:</p>
<p>编译器可能会改变两个操作的先后顺序; </p>
<p>处理器也可能不会按照目标代码的顺序执行; </p>
<p>这种一个处理器上执行的多个操作,在其他处理器来看它的顺序与目标代码指定的顺序可能不一样,这种现象称为重排序.</p>
<h3 id="指令重排序"><a href="#指令重排序" class="headerlink" title="指令重排序"></a><strong>指令重排序</strong></h3><p>在源码顺序与程序顺序不一致,或者程序顺序与执行顺序不一致的情况下,我们就说发生了指令重排序(Instruction Reorder)。</p>
<p>javac 编译器一般不会执行指令重排序, 而 JIT 编译器可能执行指令重排序.处理器也可能执行指令重排序, 使得执行顺序与程序顺序不一致。</p>
<h3 id="保证内存访问的顺序性"><a href="#保证内存访问的顺序性" class="headerlink" title="保证内存访问的顺序性"></a><strong>保证内存访问的顺序性</strong></h3><p>可以使用 volatile 关键字, synchronized 关键字实现有序性</p>
<h2 id="线程同步"><a href="#线程同步" class="headerlink" title="线程同步"></a><strong>线程同步</strong></h2><p>Java平台提供的线程同步机制包括:锁,volatile关键字,final关键字,static关键字,以及相关的 API,如 Object.wait()/Object.notify()等</p>
<h2 id="锁概念"><a href="#锁概念" class="headerlink" title="锁概念"></a><strong>锁概念</strong></h2><p>锁(Lock)可以理解为对共享数据进行保护的一个许可证。 对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须先持有该许可证. 一个线程只有在持有许可证的情况下才能对这些共享数据进行访问; 并且一个许可证一次只能被一个线程持有; 许可证线程在结束对共享数据的访问后必须释放其持有的许可证。</p>
<p>一线程在访问共享数据前必须先获得锁; 获得锁的线程称为锁的持有线程; 一个锁一次只能被一个线程持有. 锁的持有线程在获得锁之后和释放锁之前这段时间所执行的代码称为临界区(Critical Section)</p>
<p>锁具有排他性(Exclusive), 即一个锁一次只能被一个线程持有.这 种锁称为排它锁或互斥锁(Mutex).</p>
<p>JVM 把锁分为内部锁和显示锁两种. 内部锁通过 synchronized 关键字实现; 显示锁通过 java.concurrent.locks.Lock 接口的实现类实现的</p>
<h2 id="锁的作用"><a href="#锁的作用" class="headerlink" title="锁的作用"></a><strong>锁的作用</strong></h2><p>锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性。</p>
<p>注意，使用锁保障线程的安全性,必须满足以下条件:</p>
<ol>
<li><p>这些线程在访问共享数据时必须使用同一个锁</p>
</li>
<li><p>即使是读取共享数据的线程也需要使用同步锁</p>
</li>
</ol>
<h2 id="锁的相关概念"><a href="#锁的相关概念" class="headerlink" title="锁的相关概念"></a><strong>锁的相关概念</strong></h2><p><strong>可重入性</strong>：一个线程持有该锁的时候能再次(多次)申请该锁。如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的, 否则就称该锁为不可重入的。</p>
<p><strong>锁的争用与调度</strong>：Java 平台中内部锁属于非公平锁, 显示 Lock 锁既支持公平锁又支 持非公平锁</p>
<p><strong>锁的粒度</strong>：一个锁可以保护的共享数据的数量大小称为锁的粒度。锁保护共享数据量大,称该锁的粒度粗, 否则就称该锁的粒度细。锁的粒度过粗会导致线程在申请锁时会进行不必要的等待.锁的 粒度过细会增加锁调度的开销。</p>
<h2 id="内部锁-synchronized-关键字"><a href="#内部锁-synchronized-关键字" class="headerlink" title="内部锁:synchronized 关键字"></a><strong>内部锁:synchronized 关键字</strong></h2><p>Java 中的每个对象都有一个与之关联的内部锁(Intrinsic lock)，这种锁也称为监视器(Monitor)，这种内部锁是一种排他锁，可以保障原子性，可见性与有序性。</p>
<p>内部锁是通过 synchronized 关键字实现的。synchronized 关键字修饰代码块,修饰该方法。</p>
<p>修饰代码块的语法:</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">synchronized</span><span class="params">( 对象锁 )</span></span> &#123;</span><br><span class="line">    同步代码块,可以在同步代码块中访问共享数据</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>修饰实例方法就称为同步实例方法、修饰静态方法称称为同步静态方法</p>
<h3 id="synchronized-同步代码块"><a href="#synchronized-同步代码块" class="headerlink" title="synchronized 同步代码块"></a><strong>synchronized 同步代码块</strong></h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">main</span>(<span class="params"><span class="built_in">String</span>[] args</span>) &#123;</span><br><span class="line">        <span class="comment">//创建两个线程,分别调用 mm()方法</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//先创建 Test01 对象,通过对象名调用 mm()方法</span></span><br><span class="line">        <span class="title class_">Test01</span> obj = <span class="keyword">new</span> <span class="title class_">Test01</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                <span class="comment">//使用的锁对象this就是obj对象</span></span><br><span class="line">                <span class="comment">//谁调用，锁对象就是谁</span></span><br><span class="line">                obj.<span class="title function_">mm</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                obj.<span class="title function_">mm</span>(); <span class="comment">//使用的锁对象this也是obj对象</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义方法,打印 100 行字符串</span></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">mm</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="comment">//经常使用this当前对象作为锁对象</span></span><br><span class="line">        synchronized (<span class="variable language_">this</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (int i = <span class="number">1</span>; i &lt;= <span class="number">100</span>; i++) &#123;</span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="title class_">Thread</span>.<span class="title function_">currentThread</span>().<span class="title function_">getName</span>() + <span class="string">&quot; --&gt; &quot;</span> + i);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果线程的锁不同，不能实现同步</p>
<p>想要同步必须使用同一个锁对象</p>
<p>类的成员变量是放在堆中的：new出来一个对象，都会保存到堆中，而堆中记录了这个对象的成员变量。相同类new出来的不同对象，成员变量互不干扰。</p>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> Test02 &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">int</span> <span class="keyword">count</span> = <span class="number">100</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> main(String[] args) &#123;</span><br><span class="line">        <span class="comment">//先创建 Test01 对象</span></span><br><span class="line">        Test02 obj = <span class="keyword">new</span> Test02();</span><br><span class="line">        Test02 obj2 = <span class="keyword">new</span> Test02();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> run() &#123;</span><br><span class="line">                <span class="comment">//obj.mm();//使用的锁对象this就是obj对象</span></span><br><span class="line">                <span class="keyword">synchronized</span> (obj) &#123; <span class="comment">//经常使用this当前对象作为锁对象</span></span><br><span class="line">                    <span class="keyword">while</span> (<span class="keyword">count</span> &gt; <span class="number">0</span>)&#123;</span><br><span class="line">                        System.out.<span class="keyword">println</span>(<span class="string">&quot;当前count为：&quot;</span>+<span class="keyword">count</span>--);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> run() &#123;</span><br><span class="line">                <span class="comment">//obj2.mm(); //使用的锁对象 this 也是 obj2对象</span></span><br><span class="line">                <span class="keyword">synchronized</span> (obj2) &#123; <span class="comment">//经常使用this当前对象作为锁对象</span></span><br><span class="line">                    <span class="keyword">while</span> (<span class="keyword">count</span> &gt; <span class="number">0</span>)&#123;</span><br><span class="line">                        System.out.<span class="keyword">println</span>(<span class="string">&quot;当前count为：&quot;</span>+<span class="keyword">count</span>--);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>常量也可以作为锁对象<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test03</span> &#123;</span><br><span class="line">    int count = <span class="number">100</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">main</span>(<span class="params"><span class="built_in">String</span>[] args</span>) &#123;</span><br><span class="line">        <span class="comment">//创建两个线程,分别调用 mm()方法</span></span><br><span class="line">        <span class="comment">//先创建 Test01 对象,通过对象名调用 mm()方法</span></span><br><span class="line">        <span class="title class_">Test03</span> obj = <span class="keyword">new</span> <span class="title class_">Test03</span>();</span><br><span class="line">        <span class="title class_">Test03</span> obj2 = <span class="keyword">new</span> <span class="title class_">Test03</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                obj.<span class="title function_">mm</span>(); <span class="comment">//使用的锁对象 OBJ 常量</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                obj2.<span class="title function_">mm</span>(); <span class="comment">//使用的锁对象 OBJ 常量</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> final <span class="title class_">Object</span> <span class="variable constant_">OBJ</span> = <span class="keyword">new</span> <span class="title class_">Object</span>();<span class="comment">//定义一个常量</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//定义方法,打印 100 行字符串</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">mm</span>(<span class="params"></span>) &#123;</span><br><span class="line">        synchronized (<span class="variable constant_">OBJ</span>) &#123; <span class="comment">//使用一个常量对象作为锁对象</span></span><br><span class="line">            <span class="keyword">while</span> (count &gt; <span class="number">0</span>)&#123;</span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="string">&quot;当前count = &quot;</span> +count--);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>把整个方法体作为同步代码块<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">默认的锁对象是当前类的运行时类对象</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test06</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        <span class="comment">//先创建 Test01 对象,通过对象名调用 mm()方法</span></span><br><span class="line">        Test06 obj = <span class="keyword">new</span> Test06();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//一个线程调用 m1()方法</span></span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                obj.m1(); <span class="comment">//使用的锁对象是 Test06.class</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//另一个线程调用 sm2()方法</span></span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                Test06.sm2(); <span class="comment">//使用的锁对象是 Test06.class</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义方法,打印 100 行字符串</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">m1</span>()</span>&#123;</span><br><span class="line">        <span class="comment">//使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象</span></span><br><span class="line">        synchronized ( Test06.<span class="keyword">class</span> ) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">100</span>; i++) &#123;</span><br><span class="line">                System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot; --&gt; &quot;</span> + i);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类Test06.class 作为锁对象</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> synchronized <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sm2</span>()</span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">100</span>; i++) &#123;</span><br><span class="line">            System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot; --&gt; &quot;</span> + i);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>同步方法锁的粒度粗, 执行效率低, 同步代码块执行效率高</p>
<h2 id="脏读"><a href="#脏读" class="headerlink" title="脏读"></a><strong>脏读</strong></h2><p>出现读取属性值出现了一些意外, 读取的是中间值,而不是修改之后的值。出现脏读的原因是对共享数据的修改与对共享数据的读取不同步</p>
<p>解决方法:</p>
<p>不仅对修改数据的代码块进行同步,还要对读取数据的代码块同步。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test08</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="comment">//开启子线程设置用户名和密码</span></span><br><span class="line">        <span class="type">PublicValue</span> <span class="variable">publicValue</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PublicValue</span>();</span><br><span class="line">        <span class="type">SubThread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SubThread</span>(publicValue);</span><br><span class="line">        t1.start();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//为了确定设置成功</span></span><br><span class="line">        Thread.sleep(<span class="number">100</span>);</span><br><span class="line">        <span class="comment">//在 main 线程中读取用户名,密码</span></span><br><span class="line">        publicValue.getValue();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义线程,设置用户名和密码</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">SubThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> PublicValue publicValue;</span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">SubThread</span><span class="params">( PublicValue publicValue)</span>&#123;</span><br><span class="line">            <span class="built_in">this</span>.publicValue = publicValue;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            publicValue.setValue(<span class="string">&quot;bjpowernode&quot;</span>, <span class="string">&quot;123&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">PublicValue</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> <span class="string">&quot;wkcto&quot;</span>;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">String</span> <span class="variable">pwd</span> <span class="operator">=</span> <span class="string">&quot;666&quot;</span>;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">getValue</span><span class="params">()</span>&#123;</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot;,getter -- name: &quot;</span> + name + <span class="string">&quot;,--pwd: &quot;</span> + pwd);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">setValue</span><span class="params">(String name, String pwd)</span>&#123;</span><br><span class="line">            <span class="built_in">this</span>.name = name;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>); <span class="comment">//模拟操作name 属性需要一定时间</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">this</span>.pwd = pwd;</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot;,setter --name:&quot;</span> + name + <span class="string">&quot;, --pwd: &quot;</span> + pwd );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>线程出现异常会自动释放锁</strong></p>
<h2 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a><strong>死锁</strong></h2><p>在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁</p>
<p>如何避免死锁?</p>
<p>当需要获得多个锁时,所有线程获得锁的顺序保持一致即可。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test10</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">SubThread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SubThread</span>();</span><br><span class="line">        t1.setName(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">        t1.start();</span><br><span class="line">        <span class="type">SubThread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SubThread</span>();</span><br><span class="line">        t2.setName(<span class="string">&quot;b&quot;</span>);</span><br><span class="line">        t2.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">SubThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;a&quot;</span>.equals(Thread.currentThread().getName())) &#123;</span><br><span class="line">                <span class="keyword">synchronized</span> (lock1) &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;a 线程获得了 lock1 锁,还需 要获得 lock2 锁&quot;</span>);</span><br><span class="line">                    <span class="keyword">synchronized</span> (lock2) &#123;</span><br><span class="line">                        System.out.println(<span class="string">&quot;a 线程获得 lock1 后又 获得了 lock2,可以想干任何想干的事&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;b&quot;</span>.equals(Thread.currentThread().getName())) &#123;</span><br><span class="line">                <span class="keyword">synchronized</span> (lock2) &#123;</span><br><span class="line">                    System.out.println(<span class="string">&quot;b 线程获得了 lock2 锁,还需要获得 lock1 锁&quot;</span>);</span><br><span class="line">                    <span class="keyword">synchronized</span> (lock1) &#123;</span><br><span class="line">                        System.out.println(<span class="string">&quot;b 线程获得lock2后又 获得了 lock1,可以想干任何想干的事&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>获得锁的顺序不同，可能导致死锁的发生。</p>
<h1 id="轻量级同步机制-volative-关键字"><a href="#轻量级同步机制-volative-关键字" class="headerlink" title="轻量级同步机制:volative 关键字"></a><strong>轻量级同步机制:volative 关键字</strong></h1><h2 id="volatile-的作用"><a href="#volatile-的作用" class="headerlink" title="volatile 的作用"></a><strong>volatile 的作用</strong></h2><p>volatile 关键的作用使变量在多个线程之间可见</p>
<p>volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取。</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test02</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">main</span>(<span class="params"><span class="built_in">String</span>[] args</span>) &#123;</span><br><span class="line">        <span class="comment">//创建 PrintString 对象</span></span><br><span class="line">        <span class="title class_">PrintString</span> printString = <span class="keyword">new</span> <span class="title class_">PrintString</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//开启子线程,让子线程执行 printString 对象的 printStringMethod()方法</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                printString.<span class="title function_">printStringMethod</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//main 线程睡眠 1000 毫秒</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="title class_">Thread</span>.<span class="title function_">sleep</span>(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (<span class="title class_">InterruptedException</span> e) &#123;</span><br><span class="line">            e.<span class="title function_">printStackTrace</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="string">&quot;在 main 线程中修改打印标志&quot;</span>);</span><br><span class="line">        printString.<span class="title function_">setContinuePrint</span>(<span class="literal">false</span>);</span><br><span class="line">        <span class="comment">//程序运行,查看在 main 线程中修改了打印标志之后 ,子线程打印是否可以结束打印</span></span><br><span class="line">        <span class="comment">//程序运行后, 可能会出现死循环情况</span></span><br><span class="line">        <span class="comment">//分析原因: main 线程修改了 printString 对象的打印标志后, 子线程读不到</span></span><br><span class="line">        <span class="comment">//解决办法: 使用 volatile 关键字修饰 printString 对象的打印标志.</span></span><br><span class="line">        <span class="comment">//volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//定义类打印字符串</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">PrintString</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> volatile <span class="built_in">boolean</span> continuePrint = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title class_">PrintString</span> <span class="title function_">setContinuePrint</span>(<span class="params"><span class="built_in">boolean</span> continuePrint</span>) &#123;</span><br><span class="line">            <span class="variable language_">this</span>.<span class="property">continuePrint</span> = continuePrint;</span><br><span class="line">            <span class="keyword">return</span> <span class="variable language_">this</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">printStringMethod</span>(<span class="params"></span>)&#123;</span><br><span class="line">            <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="title class_">Thread</span>.<span class="title function_">currentThread</span>().<span class="title function_">getName</span>() + <span class="string">&quot;开始....&quot;</span>);</span><br><span class="line">            <span class="keyword">while</span> (continuePrint)&#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="title class_">Thread</span>.<span class="title function_">currentThread</span>().<span class="title function_">getName</span>() + <span class="string">&quot;结束++++++++++++++&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如果不用volatile修饰的话，该程序将陷入死循环。因为该线程是从工作内存读取continuePrint的值，而我们在其他线程中修改过continuePrint后，该线程看不到。</p>
<p><strong>volatile 与 synchronized 比较</strong></p>
<ol>
<li><p>volatile 关键字是线程同步的轻量级实现,所以volatile性能肯定 比 synchronized 要好; volatile 只能修饰变量,而 synchronized 可以修 饰方法,代码块. 随着 JDK 新版本的发布,synchronized 的执行效率也 有较大的提升,在开发中使用 sychronized 的比率还是很大的。</p>
</li>
<li><p>多线程访问 volatile 变量不会发生阻塞,而 synchronized 可能会阻塞</p>
</li>
<li><p>volatile 能保证数据的可见性,但是不能保证原子性;而 synchronized 可以保证原子性,也可以保证可见性</p>
</li>
<li><p>关键字 volatile 解决的是变量在多个线程之间的可见性;synchronized 关键字解决多个线程之间访问公共资源的同步性</p>
</li>
</ol>
<h2 id="volatile-非原子特性"><a href="#volatile-非原子特性" class="headerlink" title="volatile 非原子特性"></a><strong>volatile 非原子特性</strong></h2><p>volatile 关键字增加了实例变量在多个线程之间的可见性,但是不具备原子性</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test03</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        <span class="comment">//在 main 线程中创建 10 个子线程</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> MyThread().start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">MyThread</span> <span class="title">extends</span> <span class="title">Thread</span>&#123;</span><br><span class="line">        <span class="comment">//volatile 关键仅仅是表示所有线程从主内存读取 count 变量的值</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">volatile</span> <span class="keyword">static</span> <span class="built_in">int</span> count;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/*这段代码运行后不是线程安全的,想要线程安全,需要使用 synchronized 进行同步,如果使用 synchronized 同时,也就不需要 volatile 关键了*/</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">addCount</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">                <span class="comment">//count++不是原子操作</span></span><br><span class="line">                count++;</span><br><span class="line">            &#125;</span><br><span class="line">            System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot; count=&quot;</span> + count);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line"><span class="comment">//        该方法安全</span></span><br><span class="line"><span class="comment">//        public synchronized static void addCount()&#123;</span></span><br><span class="line"><span class="comment">//            for (int i = 0; i &lt; 1000; i++) &#123;</span></span><br><span class="line"><span class="comment">//                //count++不是原子操作</span></span><br><span class="line"><span class="comment">//                count++;</span></span><br><span class="line"><span class="comment">//            &#125;</span></span><br><span class="line"><span class="comment">//            System.out.println(Thread.currentThread().getName() + &quot; count=&quot; + count);</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line">        @Override</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">            addCount();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="常用原子类进行自增自减操作"><a href="#常用原子类进行自增自减操作" class="headerlink" title="常用原子类进行自增自减操作"></a><strong>常用原子类进行自增自减操作</strong></h2><p>我们知道 i++操作不是原子操作, 除了使用 Synchronized 进行同步 外,也可以使用 AtomicInteger/AtomicLong 原子类进行实现</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test04</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>) throws InterruptedException</span>&#123;</span><br><span class="line">        <span class="comment">//在 main 线程中创建 10 个子线程</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> MyThread().start();</span><br><span class="line">        &#125;</span><br><span class="line">        Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        System.<span class="keyword">out</span>.println( MyThread.count.<span class="keyword">get</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">MyThread</span> <span class="title">extends</span> <span class="title">Thread</span>&#123;</span><br><span class="line">        <span class="comment">//static int a = 0;</span></span><br><span class="line">        <span class="comment">//使用 AtomicInteger 对象</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> AtomicInteger count = <span class="keyword">new</span> AtomicInteger();</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">addCount</span>()</span>&#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;</span><br><span class="line">                <span class="comment">//自增的后缀形式</span></span><br><span class="line">                count.getAndIncrement();</span><br><span class="line">            &#125;</span><br><span class="line">            System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot; count=&quot;</span> + count.<span class="keyword">get</span>());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">        </span><br><span class="line"><span class="comment">//       该方法和下面的方法都是用的同步原理        </span></span><br><span class="line"><span class="comment">//        public static synchronized void addCount()&#123;</span></span><br><span class="line"><span class="comment">//            for (int i = 0; i &lt; 1000; i++) &#123;</span></span><br><span class="line"><span class="comment">//                //自增的后缀形式</span></span><br><span class="line"><span class="comment">//                a++;</span></span><br><span class="line"><span class="comment">//            &#125;</span></span><br><span class="line"><span class="comment">//            System.out.println(Thread.currentThread().getName() + &quot; a=&quot; + a);</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//        public synchronized static void addCount()&#123;</span></span><br><span class="line"><span class="comment">//            for (int i = 0; i &lt; 1000; i++) &#123;</span></span><br><span class="line"><span class="comment">//                //自增的后缀形式</span></span><br><span class="line"><span class="comment">//                count.getAndIncrement();</span></span><br><span class="line"><span class="comment">//            &#125;</span></span><br><span class="line"><span class="comment">//            System.out.println(Thread.currentThread().getName() + &quot; count=&quot; + count.get());</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">            addCount();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="线程同步-1"><a href="#线程同步-1" class="headerlink" title="线程同步"></a><strong>线程同步</strong></h1><h2 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a><strong>CAS</strong></h2><p>CAS(Compare And Swap)是由硬件实现的</p>
<p>CAS 可以将 read- modify - write 这类的操作转换为原子操作</p>
<p>i++自增操作包括三个子操作: 从主内存读取 i 变量值 对 i 的值加 1 再把加 1 之后 的值保存到主内存</p>
<p>CAS 原理: 在把数据更新到主内存时,再次读取主内存变量的值,如 果现在变量的值与期望的值(操作起始时读取的值)一样就更新</p>
<img src="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/CAS%E5%8E%9F%E7%90%86.png" class title="CAS原理">
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">CASTest</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        CASCounter casCounter = <span class="keyword">new</span> CASCounter();</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100000</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                    System.<span class="keyword">out</span>.println(casCounter.incrementAndGet());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title">CASCounter</span> &#123;</span><br><span class="line">    <span class="comment">//使用 volatile 修饰 value 值,使线程可见</span></span><br><span class="line">    <span class="keyword">volatile</span> <span class="keyword">private</span> <span class="built_in">long</span> <span class="keyword">value</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">getValue</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">value</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//定义 comare and swap 方法</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> boolean <span class="title">compareAndSwap</span>(<span class="params"><span class="built_in">long</span> expectedValue, <span class="built_in">long</span> newValue</span>)</span> &#123;</span><br><span class="line">        <span class="comment">//如果当前 value 的值与期望的 expectedVAlue 值一样,就把当前的 Value 字段替换为 newValue 值</span></span><br><span class="line">        synchronized (<span class="keyword">this</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">value</span> == expectedValue) &#123;</span><br><span class="line">                <span class="keyword">value</span> = newValue;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义自增的方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">incrementAndGet</span>()</span>&#123;</span><br><span class="line">        <span class="built_in">long</span> oldvalue ;</span><br><span class="line">        <span class="built_in">long</span> newValue;</span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            oldvalue = <span class="keyword">value</span>;</span><br><span class="line">            newValue = oldvalue+<span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">while</span> (!compareAndSwap(oldvalue, newValue));</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> newValue;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>自己实现了一个CAS原理。用volatile从内存中读取数据，保证可见性。在自增方法中做自增操作以后，将自增后的值写入内存的方法用sychronized来实现，在该方法中检查内存中的值和预期中的值是否一致，不一致则撤销更新，一致则更新内存中的值。该操作可以保证多个线程同步执行，不会出现多线程同时写数据的情况。</p>
<p>CAS 实现原子操作背后有一个假设: 共享变量的当前值与当前线 程提供的期望值相同, 就认为这个变量没有被其他线程修改过。实际上这种假设不一定总是成立.如有共享变量 count = 0</p>
<p>A 线程对 count 值修改为 10</p>
<p>B 线程对 count 值修改为 20</p>
<p>C 线程对 count 值修改为 0</p>
<p>当前线程看到 count 变量的值现在是 0,现在是否认为 count 变量的值没有被其他线程更新呢? 这种结果是否能够接受?</p>
<p><strong>这就是 CAS 中的 ABA 问题,即共享变量经历了 A-&gt;B-&gt;A 的更新。</strong></p>
<p>是否能够接收 ABA 问题跟实现的算法有关。</p>
<p>如果想要规避 ABA 问题,可以为共享变量引入一个修订号(时间 戳), 每次修改共享变量时,相应的修订号就会增加 1. ABA 变量更 新过程变量: [A,0] -&gt;[B,1]-&gt;[A,2], 每次对共享变量的修改都会导致 修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过。 <code>AtomicStampedReference</code> 类就是基于这种思想产生的</p>
<h2 id="原子变量类"><a href="#原子变量类" class="headerlink" title="原子变量类"></a><strong>原子变量类</strong></h2><p>原子变量类基于CAS实现的, 当对共享变量进行read-modify-write 更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量 的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而 是变量的新值依赖变量的旧值,如自增操作i++. 由于volatile只能保证 可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量, 并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变 量类看作增强的 volatile 变量. 原子变量类有 12 个</p>
<ol>
<li>基础数据型：<ul>
<li>AtomicInteger</li>
<li>AtomicLong</li>
<li>AtomicBoolean</li>
</ul>
</li>
<li>数组型<ul>
<li>AtomicIntegerArray</li>
<li>AtomicLongArray</li>
<li>AtomicReferenceArray</li>
</ul>
</li>
<li>字段更新器<ul>
<li>AtomicIntegerFieldUpdater</li>
<li>AtomicLongFieldUpdater</li>
<li>AtomicReferenceFieldUpdater</li>
</ul>
</li>
<li>引用型<ul>
<li>AtomicReference</li>
<li>AtomicStampedReference</li>
<li>AtomicMarkableReference</li>
</ul>
</li>
</ol>
<p><strong>AtomicLong使用演示：</strong></p>
<p>使用原子变量类定义一个计数器,该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以设计为单例</p>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Indicator</span> &#123;</span><br><span class="line">    <span class="comment">//构造方法私有化</span></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Indicator</span>()</span>&#123;&#125; <span class="comment">//定义一个私有的本类静态的对象</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> final Indicator INSTANCE = <span class="keyword">new</span> Indicator();</span><br><span class="line">    <span class="comment">//3)提供一个公共静态方法返回该类唯一实例</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Indicator <span class="title">getInstance</span>()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//使用原子变量类保存请求总数,成功数,失败数</span></span><br><span class="line">    <span class="keyword">private</span> final AtomicLong requestCount = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>); <span class="comment">//记录请求总数</span></span><br><span class="line">    <span class="keyword">private</span> final AtomicLong successCount = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>); <span class="comment">//处理成功总数</span></span><br><span class="line">    <span class="keyword">private</span> final AtomicLong fialureCount = <span class="keyword">new</span> AtomicLong(<span class="number">0</span>); <span class="comment">//处理失败总数</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//有新的请求</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">newRequestReceive</span>()</span>&#123;</span><br><span class="line">        requestCount.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理成功</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">requestProcessSuccess</span>()</span>&#123;</span><br><span class="line">        successCount.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理失败</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">requestProcessFailure</span>()</span>&#123;</span><br><span class="line">        fialureCount.incrementAndGet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//查看总数,成功数,失败数</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">getRequestCount</span>()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> requestCount.<span class="keyword">get</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">getSuccessCount</span>()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> successCount.<span class="keyword">get</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="built_in">long</span> <span class="title">getFailureCount</span>()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> fialureCount.<span class="keyword">get</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//模拟服务器的请求总数, 处理成功数,处理失败数</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        <span class="comment">//通过线程模拟请求,在实际应用中可以在 ServletFilter 中调用 Indicator 计数器的相关方法</span></span><br><span class="line">        <span class="comment">//模拟10000个线程对服务器发送请求</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                    <span class="comment">//每个线程就是一个请求,请求总数要加 1</span></span><br><span class="line">                    Indicator.getInstance().newRequestReceive();</span><br><span class="line">                    <span class="built_in">int</span> num = <span class="keyword">new</span> Random().nextInt();</span><br><span class="line">                    <span class="keyword">if</span> (num % <span class="number">2</span> == <span class="number">0</span>)&#123;</span><br><span class="line">                        <span class="comment">//偶数模拟成功</span></span><br><span class="line">                        Indicator.getInstance().requestProcessSuccess();</span><br><span class="line">                    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="comment">//处理失败</span></span><br><span class="line">                        Indicator.getInstance().requestProcessFailure();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//打印结果</span></span><br><span class="line">        System.<span class="keyword">out</span>.println( Indicator.getInstance().getRequestCount()); <span class="comment">//总的请求数</span></span><br><span class="line">        System.<span class="keyword">out</span>.println( Indicator.getInstance().getSuccessCount()); <span class="comment">//成功数</span></span><br><span class="line">        System.<span class="keyword">out</span>.println( Indicator.getInstance().getFailureCount()); <span class="comment">//失败数</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>AtomicIntegerArray使用演示：</strong><br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123; <span class="comment">//1)创建一个指定长度的原子数组</span></span><br><span class="line">        AtomicIntegerArray atomicIntegerArray = <span class="keyword">new</span> <span class="constructor">AtomicIntegerArray(10)</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(atomicIntegerArray); <span class="comment">//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="comment">// 2)返回指定位置的元素</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get(<span class="number">0</span>)); <span class="comment">//0</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get(<span class="number">1</span>)); <span class="comment">//0</span></span><br><span class="line">        <span class="comment">// 3)设置指定位置的元素</span></span><br><span class="line">        atomicIntegerArray.set(<span class="number">0</span>, <span class="number">10</span>);</span><br><span class="line">        <span class="comment">//在设置数组元素的新值时, 同时返回数组元素的旧值</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get<span class="constructor">AndSet(1, 11)</span> ); <span class="comment">//0</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray ); <span class="comment">//[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="comment">// 4)修改数组元素的值,把数组元素加上某个值,做加法运算</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.add<span class="constructor">AndGet(0, 22)</span> ); <span class="comment">//32   先做加法运算再返回该下标的值</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get<span class="constructor">AndAdd(1, 33)</span>); <span class="comment">//11    先返回该下标的值后做加法运算</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray ); <span class="comment">//[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="comment">// 5)CAS 操作</span></span><br><span class="line">        <span class="comment">// 如果数组中索引值为 0 的元素的值是 32 , 就修改为 222</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.compare<span class="constructor">AndSet(0, 32, 222)</span>); <span class="comment">//true</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray ); <span class="comment">//[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.compare<span class="constructor">AndSet(1, 11, 333)</span>); <span class="comment">//false</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(atomicIntegerArray); </span><br><span class="line">        <span class="comment">// 6)自增/自减</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.increment<span class="constructor">AndGet(0)</span>); <span class="comment">//223, 相当于前缀  先自增后获取该下标的值</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get<span class="constructor">AndIncrement(1)</span>); <span class="comment">//44, 相当于后缀  先获取该下标的值后自增</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray ); <span class="comment">//[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.decrement<span class="constructor">AndGet(2)</span>); <span class="comment">//-1             先自减后获取该下标的值</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray); <span class="comment">//[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray.get<span class="constructor">AndDecrement(3)</span>); <span class="comment">//0              先获取该下标的值后自减</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( atomicIntegerArray ); <span class="comment">//[223, 45, -1, -1, 0, 0, 0, 0, 0, 0] &#125;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>在多线程中使用 AtomicIntegerArray 原子数组:<br><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test02</span> &#123;</span><br><span class="line">    <span class="comment">//定义原子数组</span></span><br><span class="line">    <span class="type">static</span> AtomicIntegerArray atomicIntegerArray = <span class="keyword">new</span> <span class="built_in">AtomicIntegerArray</span>(<span class="number">10</span>);</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="type">static</span> <span class="type">void</span> <span class="title">main</span><span class="params">(<span class="type">String</span>[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//定义线程数组</span></span><br><span class="line">        Thread[] threads = <span class="keyword">new</span> Thread[<span class="number">10</span>];</span><br><span class="line">        <span class="comment">//给线程数组元素赋值</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; threads.length; i++) &#123;</span><br><span class="line">            threads[i] = <span class="keyword">new</span> <span class="built_in">AddThread</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//开启子线程</span></span><br><span class="line">        <span class="keyword">for</span> (Thread thread : threads) &#123;</span><br><span class="line">            thread.<span class="built_in">start</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//在主线程中查看自增完以后原子数组中的各个元素的值,在主线程中需要在所有子线程都执行完后再查看</span></span><br><span class="line">        <span class="comment">//把所有的子线程合并到当前主线程中</span></span><br><span class="line">        <span class="keyword">for</span> (Thread thread : threads) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                thread.<span class="built_in">join</span>();<span class="comment">//等待子线程执行完再往下执行</span></span><br><span class="line">            &#125; <span class="built_in">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.<span class="built_in">printStackTrace</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.<span class="built_in">println</span>( atomicIntegerArray );</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义一个线程类,在线程类中修改原子数组</span></span><br><span class="line">    <span class="type">static</span> <span class="keyword">class</span> <span class="title class_">AddThread</span> extends Thread&#123;</span><br><span class="line">        @<span class="function">Override</span></span><br><span class="line"><span class="function">        <span class="keyword">public</span> <span class="type">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">//把原子数组的每个元素自增 1000 次</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; <span class="number">1000</span>; j++) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; atomicIntegerArray.<span class="built_in">length</span>(); i++) &#123;</span><br><span class="line">                    atomicIntegerArray.<span class="built_in">getAndIncrement</span>(i % atomicIntegerArray.<span class="built_in">length</span>());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">/* for (int i = 0; i &lt; 10000; i++) &#123;</span></span><br><span class="line"><span class="comment">                    atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());</span></span><br><span class="line"><span class="comment">                &#125;*/</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>AtomicIntegerFieldUpdater使用演示：</strong></p>
<p>AtomicIntegerFieldUpdater 可以对原子整数字段进行更新,要求：</p>
<ol>
<li>字符必须使用 volatile 修饰,使线程之间可见</li>
<li>只能是实例变量,不能是静态变量,也不能使用 final 修饰<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//要操作的类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">User</span> &#123;</span><br><span class="line">    <span class="built_in">int</span> id;</span><br><span class="line">    <span class="keyword">volatile</span> <span class="built_in">int</span> age;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span>(<span class="params"><span class="built_in">int</span> id, <span class="built_in">int</span> age</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.id = id; <span class="keyword">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line">    @Override</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">toString</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;User&#123;&quot;</span> + <span class="string">&quot;id=&quot;</span> + id +<span class="string">&quot;, age=&quot;</span> + age +<span class="string">&#x27;&#125;&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//线程类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">SubThread</span> <span class="title">extends</span> <span class="title">Thread</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> User user; <span class="comment">//要更新的 User 对象</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//创建 AtomicIntegerFieldUpdater 更新器</span></span><br><span class="line">    <span class="keyword">private</span> AtomicIntegerFieldUpdater&lt;User&gt; updater = AtomicIntegerFieldUpdater.newUpdater(User.<span class="keyword">class</span>, <span class="string">&quot;age&quot;</span>);</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">SubThread</span>(<span class="params">User user</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.user = user;</span><br><span class="line">    &#125;</span><br><span class="line">    @Override</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">        <span class="comment">//在子线程中对 user 对象的 age 字段自增 10 次</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;的值为&quot;</span> + updater.getAndIncrement(user));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//主类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        User user = <span class="keyword">new</span> User(<span class="number">1234</span>, <span class="number">10</span>);</span><br><span class="line">        <span class="comment">//开启 10 个线程</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> SubThread(user).start();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.<span class="keyword">out</span>.println( user );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h1 id="线程间的通信"><a href="#线程间的通信" class="headerlink" title="线程间的通信"></a><strong>线程间的通信</strong></h1><h2 id="等待-通知机制"><a href="#等待-通知机制" class="headerlink" title="等待/通知机制"></a><strong>等待/通知机制</strong></h2><h3 id="什么是等待通知机制"><a href="#什么是等待通知机制" class="headerlink" title="什么是等待通知机制"></a><strong>什么是等待通知机制</strong></h3><p>在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在 if 语句块中。在多线程编程中,可能 A 线程的条件没有满足只是暂时的, 稍后其 他的线程 B 可能会更新条件使得 A 线程的条件得到满足. 可以将 A 线 程暂停,直到它的条件得到满足后再将 A 线程唤醒.它的伪代码：<br><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">atomics&#123; <span class="regexp">//</span>原子操作</span><br><span class="line">    <span class="keyword">while</span>( 条件不成立 )&#123;</span><br><span class="line">        等待</span><br><span class="line">    &#125;</span><br><span class="line">    当前线程被唤醒条件满足后,继续执行下面的操作</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="等待-通知机制的实现"><a href="#等待-通知机制的实现" class="headerlink" title="等待/通知机制的实现"></a><strong>等待/通知机制的实现</strong></h3><p>Object 类中的 wait()方法可以使执行当前代码的线程等待,暂停执 行,直到接到通知或被中断为止.</p>
<p>注意:</p>
<ol>
<li>wait()方法只能在同步代码块中由锁对象调用</li>
<li>调用 wait()方法,当前线程会释放锁<figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">synchronized</span>( 锁对象 )&#123;</span><br><span class="line">    <span class="built_in">while</span>( 条件不成立 )&#123;</span><br><span class="line">        <span class="comment">//通过锁对象调用 wait()方法暂停线程,会释放锁对象</span></span><br><span class="line">        锁对象<span class="selector-class">.wait</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//线程的条件满足了继续向下执行</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
Object 类的 notify()可以唤醒线程,该方法也必须在同步代码块中 由 锁 对 象 调 用 . 没 有 使 用 锁 对 象 调 用 wait()/notify() 会 抛 出 IlegalMonitorStateExeption 异常. 如果有多个等待的线程,notify()方法 只能唤醒其中的一个. 在同步代码块中调用 notify()方法后,并不会立 即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般 将 notify()方法放在同步代码块的最后. 它的伪代码如下:<figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">synchronized</span>( 锁对象 )&#123;</span><br><span class="line">    <span class="comment">//执行修改保护条件的代码</span></span><br><span class="line">    <span class="comment">//唤醒其他线程</span></span><br><span class="line">    锁对象<span class="selector-class">.notify</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
实现：<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test03</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>) throws InterruptedException</span> &#123;</span><br><span class="line">        String <span class="keyword">lock</span> = <span class="string">&quot;wkcto&quot;</span>; <span class="comment">//定义一个字符串作为锁对象</span></span><br><span class="line">        Thread t1 = <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                synchronized (<span class="keyword">lock</span>) &#123;</span><br><span class="line">                    System.<span class="keyword">out</span>.println(<span class="string">&quot;线程 1 开始等待: &quot;</span> + System.currentTimeMillis());</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="keyword">lock</span>.wait(); <span class="comment">//线程等待,会释放锁对象,当前线程转入 blocked 阻塞状态</span></span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                    System.<span class="keyword">out</span>.println(<span class="string">&quot;线程 1 结束等待:&quot;</span> + System.currentTimeMillis());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">//定义第二个线程,在第二个线程中唤醒第一个线程</span></span><br><span class="line">        Thread t2 = <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                <span class="comment">//notify()方法也需要在同步代码块中,由锁对象调用</span></span><br><span class="line">                synchronized (<span class="keyword">lock</span>)&#123;</span><br><span class="line">                    System.<span class="keyword">out</span>.println(<span class="string">&quot;线程 2 开始唤醒 : &quot;</span> + System.currentTimeMillis());</span><br><span class="line">                    <span class="keyword">lock</span>.notify(); <span class="comment">//唤醒在 lock 锁对象上等待的某一个线程</span></span><br><span class="line">                    System.<span class="keyword">out</span>.println(<span class="string">&quot;线程 2 结束唤醒 : &quot;</span> + System.currentTimeMillis());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        t1.start(); <span class="comment">//开启 t1 线程,t1 线程等待</span></span><br><span class="line">        Thread.sleep(<span class="number">3000</span>); <span class="comment">//main 线程睡眠 3 秒,确保 t1 入睡</span></span><br><span class="line">        t2.start(); <span class="comment">//t1 线程开启 3 秒后,再开启 t2 线程唤醒 t1 线程</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
注意：<strong>notify()方法后不会立即释放锁对象</strong></li>
</ol>
<p><strong>interrupt()方法会中断 wait()</strong></p>
<h3 id="notify-与-notifyAll"><a href="#notify-与-notifyAll" class="headerlink" title="notify()与 notifyAll()"></a><strong>notify()与 notifyAll()</strong></h3><p>notify()一次只能唤醒一个线程,如果有多个等待的线程,只能随机 唤醒其中的某一个; 想要唤醒所有等待线程,需要调用 notifyAll()</p>
<h3 id="wait-long-的使用"><a href="#wait-long-的使用" class="headerlink" title="wait(long)的使用"></a><strong>wait(long)的使用</strong></h3><p>wait(long)带有 long 类型参数的 wait()等待,如果在参数指定的时间 内没有被唤醒,超时后会自动唤醒</p>
<h2 id="ThreadLocal-的使用"><a href="#ThreadLocal-的使用" class="headerlink" title="ThreadLocal 的使用"></a><strong>ThreadLocal 的使用</strong></h2><p>除了控制资源的访问外, 还可以通过增加资源来保证线程安全. ThreadLocal 主要解决为每个线程绑定自己的值.</p>
<h1 id="LOCK显示锁"><a href="#LOCK显示锁" class="headerlink" title="LOCK显示锁"></a><strong>LOCK显示锁</strong></h1><p>在 JDK5 中 增 加 了 Lock 锁 接 口 , 有 ReentrantLock 实 现 类,ReentrantLock 锁称为可重入锁, 它功能比 synchronized 多。</p>
<h2 id="锁的可重入性"><a href="#锁的可重入性" class="headerlink" title="锁的可重入性"></a><strong>锁的可重入性</strong></h2><p>锁的可重入是指,当一个线程获得一个对象锁后,再次请求该对象 锁时是可以获得该对象的锁的<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test01</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> synchronized <span class="keyword">void</span> <span class="title">sm1</span>()</span>&#123;</span><br><span class="line">        System.<span class="keyword">out</span>.println(<span class="string">&quot;同步方法1&quot;</span>);</span><br><span class="line">        <span class="comment">//线程执行 sm1()方法,默认 this 作为锁对象,在 sm1()方法中调用了 sm2()方法,注意 当前线程还是持有 this 锁对象的</span></span><br><span class="line">        <span class="comment">//sm2()同步方法默认的锁对象也是 this 对象, 要执行 sm2()必须先获得 this 锁对象, 当前 this 对象被当前线程持有,可以 再次获得 this 对象,</span></span><br><span class="line">        <span class="comment">// 这就是锁的可重入性. 假设锁不可 重入的话,可能会造成死锁</span></span><br><span class="line">        sm2();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> synchronized <span class="keyword">void</span> <span class="title">sm2</span>()</span> &#123;</span><br><span class="line">        System.<span class="keyword">out</span>.println(<span class="string">&quot;同步方法 2&quot;</span>);</span><br><span class="line">        sm3();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> synchronized <span class="keyword">void</span> <span class="title">sm3</span>()</span> &#123;</span><br><span class="line">        System.<span class="keyword">out</span>.println(<span class="string">&quot;同步方法 3&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        Test01 obj = <span class="keyword">new</span> Test01();</span><br><span class="line">        <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                obj.sm1();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="ReentrantLock"><a href="#ReentrantLock" class="headerlink" title="ReentrantLock"></a><strong>ReentrantLock</strong></h2><p>ReentrantLock的基本使用:</p>
<p>调用 lock()方法获得锁, 调用 unlock()释放锁<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test02</span> &#123;</span><br><span class="line">    <span class="comment">//定义显示锁</span></span><br><span class="line">    <span class="keyword">static</span> Lock <span class="keyword">lock</span> = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line">    <span class="comment">//定义方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sm</span>()</span>&#123;</span><br><span class="line">        <span class="comment">//先获得锁</span></span><br><span class="line">        <span class="keyword">lock</span>.<span class="keyword">lock</span>();</span><br><span class="line">        <span class="comment">//for 循环就是同步代码块</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">100</span>; i++) &#123;</span><br><span class="line">            System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot; -- &quot;</span> + i);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//释放锁</span></span><br><span class="line">        <span class="keyword">lock</span>.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        Runnable r = <span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">            @Override</span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                sm();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//启动三个线程</span></span><br><span class="line">        <span class="keyword">new</span> Thread(r).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(r).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(r).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>使用 Lock 锁同步不同方法中的同步代码块<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test03 &#123;</span><br><span class="line">    static Lock lock = <span class="keyword">new</span> <span class="constructor">ReentrantLock()</span>; <span class="comment">//定义锁对象</span></span><br><span class="line">    public static void sm1<span class="literal">()</span>&#123;</span><br><span class="line">        <span class="comment">//经常在 try 代码块中获得 Lock 锁, 在 finally 子句中释放锁</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;来到了这里&quot;</span>);</span><br><span class="line">            lock.lock<span class="literal">()</span>;</span><br><span class="line">            <span class="comment">//获得锁</span></span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;-- method 1 -- &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> );</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(1000)</span>);</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;-- method 1 -- &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> );</span><br><span class="line">        &#125; catch (InterruptedException e) &#123;</span><br><span class="line">            e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">        &#125; finally &#123;</span><br><span class="line">            lock.unlock<span class="literal">()</span>; <span class="comment">//释放锁</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void sm2<span class="literal">()</span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;来到了这里&quot;</span>);</span><br><span class="line">            lock.lock<span class="literal">()</span>;</span><br><span class="line">            <span class="comment">//获得锁</span></span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;-- method 22 -- &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> );</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(1000)</span>);</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;-- method 22 -- &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> );</span><br><span class="line">        &#125; catch (InterruptedException e) &#123;</span><br><span class="line">            e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">        &#125; finally &#123;</span><br><span class="line">            lock.unlock<span class="literal">()</span>; <span class="comment">//释放锁</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        Runnable r1 = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                sm1<span class="literal">()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        Runnable r2 = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                sm2<span class="literal">()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r1</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r1</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r1</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r2</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r2</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r2</span>)</span>.start<span class="literal">()</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="ReentrantLock-锁的可重入性"><a href="#ReentrantLock-锁的可重入性" class="headerlink" title="ReentrantLock 锁的可重入性"></a><strong>ReentrantLock 锁的可重入性</strong></h3><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test04</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Subthread</span> <span class="title">extends</span> <span class="title">Thread</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> Lock <span class="keyword">lock</span> = <span class="keyword">new</span> ReentrantLock(); <span class="comment">//定义锁对象</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">int</span> num = <span class="number">0</span>;<span class="comment">//定义变量</span></span><br><span class="line">        @Override <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt;<span class="number">10000</span> ; i++) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">//可重入锁指可以反复获得该锁</span></span><br><span class="line">                    <span class="keyword">lock</span>.<span class="keyword">lock</span>();</span><br><span class="line">                    <span class="keyword">lock</span>.<span class="keyword">lock</span>();</span><br><span class="line">                    num++;</span><br><span class="line">                &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                    <span class="keyword">lock</span>.unlock();</span><br><span class="line">                    <span class="keyword">lock</span>.unlock();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>) throws InterruptedException</span> &#123;</span><br><span class="line">        Subthread t1 = <span class="keyword">new</span> Subthread();</span><br><span class="line">        Subthread t2 = <span class="keyword">new</span> Subthread();</span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line">        t1.<span class="keyword">join</span>();</span><br><span class="line">        t2.<span class="keyword">join</span>();</span><br><span class="line">        System.<span class="keyword">out</span>.println(Subthread.num);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>lockInterruptibly()方法</p>
<p>lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁, 如果当前线程被中断则出现异常。lock方法不会被中断。<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test05 &#123;</span><br><span class="line">    static <span class="keyword">class</span> Servier&#123;</span><br><span class="line">        <span class="keyword">private</span> Lock lock = <span class="keyword">new</span> <span class="constructor">ReentrantLock()</span>; <span class="comment">//定义锁对象</span></span><br><span class="line">        public void service<span class="constructor">Method()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//lock.lock(); //获得锁定,即使调用了线程的 interrupt()方法,也没有真正的中断线程</span></span><br><span class="line">                lock.lock<span class="constructor">Interruptibly()</span>; <span class="comment">//如果线程被中断了,不会获得锁,会产生异常</span></span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;-- begin lock&quot;</span>);</span><br><span class="line">                <span class="comment">//执行一段耗时的操作</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; Integer.MAX_VALUE; i++) &#123;</span><br><span class="line">                    <span class="keyword">new</span> <span class="constructor">StringBuilder()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot; -- end lock&quot;</span>);</span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125; finally &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot; ***** 释放锁&quot;</span>);</span><br><span class="line">                lock.unlock<span class="literal">()</span>; <span class="comment">//释放锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) throws InterruptedException &#123;</span><br><span class="line">        Servier s = <span class="keyword">new</span> <span class="constructor">Servier()</span>;</span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                s.service<span class="constructor">Method()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        Thread t1 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>)</span>;</span><br><span class="line">        t1.start<span class="literal">()</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">50</span>);</span><br><span class="line">        Thread t2 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>)</span>;</span><br><span class="line">        t2.start<span class="literal">()</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">50</span>);</span><br><span class="line">        t2.interrupt<span class="literal">()</span>; <span class="comment">//中断 t2 线程</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>对于 synchronized 内部锁来说,如果一个线程在等待锁,只有两个结 果:要么该线程获得锁继续执行;要么就保持等待。对于 ReentrantLock 可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。</p>
<p><strong>通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生</strong><br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test06 &#123;</span><br><span class="line">    static <span class="keyword">class</span> IntLock implements Runnable&#123;</span><br><span class="line">        <span class="comment">//创建两个 ReentrantLock 锁对象</span></span><br><span class="line">        public static ReentrantLock lock1 = <span class="keyword">new</span> <span class="constructor">ReentrantLock()</span>;</span><br><span class="line">        public static ReentrantLock lock2 = <span class="keyword">new</span> <span class="constructor">ReentrantLock()</span>;</span><br><span class="line">        <span class="built_in">int</span> lockNum;</span><br><span class="line">        <span class="comment">//定义整数变量,决定使用哪个锁</span></span><br><span class="line">        public <span class="constructor">IntLock(<span class="params">int</span> <span class="params">lockNum</span>)</span> &#123;</span><br><span class="line">            this.lockNum = lockNum;</span><br><span class="line">        &#125;</span><br><span class="line">        @Override</span><br><span class="line">        public void run<span class="literal">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (lockNum % <span class="number">2</span><span class="operator"> == </span><span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="comment">//奇数,先锁 1,再锁 2</span></span><br><span class="line">                    lock1.lock<span class="constructor">Interruptibly()</span>;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;获得锁 1,还需要获得锁 2&quot;</span>);</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(500)</span>);</span><br><span class="line">                    lock2.lock<span class="constructor">Interruptibly()</span>;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;同时获得了锁1 与锁 2....&quot;</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123; <span class="comment">//偶数,先锁 2,再锁 1</span></span><br><span class="line">                    lock2.lock<span class="constructor">Interruptibly()</span>;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;获得锁 2,还需要获得锁 1&quot;</span>);</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(500)</span>);</span><br><span class="line">                    lock1.lock<span class="constructor">Interruptibly()</span>;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;同时获得了锁 1 与锁 2....&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;catch (InterruptedException e)&#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125;finally &#123;</span><br><span class="line">                <span class="keyword">if</span> ( lock1.is<span class="constructor">HeldByCurrentThread()</span>) <span class="comment">//判断当前线程是否持有该锁</span></span><br><span class="line">                    lock1.unlock<span class="literal">()</span>;</span><br><span class="line">                <span class="keyword">if</span> (lock2.is<span class="constructor">HeldByCurrentThread()</span>)</span><br><span class="line">                    lock2.unlock<span class="literal">()</span>;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;线程退出&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) throws InterruptedException&#123;</span><br><span class="line">        IntLock intLock1 = <span class="keyword">new</span> <span class="constructor">IntLock(11)</span>;</span><br><span class="line">        IntLock intLock2 = <span class="keyword">new</span> <span class="constructor">IntLock(22)</span>;</span><br><span class="line">        Thread t1 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">intLock1</span>)</span>;</span><br><span class="line">        Thread t2 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">intLock2</span>)</span>;</span><br><span class="line">        t1.start<span class="literal">()</span>;</span><br><span class="line">        t2.start<span class="literal">()</span>;</span><br><span class="line">        <span class="comment">//在 main 线程,等待 3000 秒,如果还有线程没有结束就中断该线程</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">3000</span>);</span><br><span class="line">        <span class="comment">//可以中断任何一个线程来解决死锁, t2 线程会放弃对锁 1 的申请,同时释放锁 2, t1 线程会完成它的任务</span></span><br><span class="line">        <span class="keyword">if</span> (t1.is<span class="constructor">Alive()</span>)&#123;</span><br><span class="line">            t1.interrupt<span class="literal">()</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="tryLock-方法"><a href="#tryLock-方法" class="headerlink" title="tryLock()方法"></a><strong>tryLock()方法</strong></h3><p>tryLock(long time, TimeUnit unit) 的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁。<strong>通过该方法可以实现锁对象的限时等待</strong><br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test07</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">TimeLock</span> <span class="title">implements</span> <span class="title">Runnable</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> ReentrantLock <span class="keyword">lock</span> = <span class="keyword">new</span> ReentrantLock(); <span class="comment">//定义锁对象</span></span><br><span class="line">        @Override</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">lock</span>.tryLock(<span class="number">3</span>, TimeUnit.SECONDS) )&#123; <span class="comment">//获得锁返回 true</span></span><br><span class="line">                    System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;获得锁,执行耗时任务&quot;</span>);</span><br><span class="line">                    <span class="comment">// Thread.sleep(4000); //假设 Thread-0 线程先持有锁,完成任务需要 4 秒钟,Thread-1 线程尝试获得锁,</span></span><br><span class="line">                    <span class="comment">// Thread-1 线程在 3 秒内还没有获得锁的话,Thread-1 线程会放弃</span></span><br><span class="line">                    <span class="comment">//假设 Thread-0 线程先持有锁,完成任 务需要 2 秒钟,Thread-1 线程尝试获得锁,Thread-1 线程会一直尝试,在它约定尝试的 3 秒内可以获得锁对象</span></span><br><span class="line">                    Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">                &#125;<span class="keyword">else</span> &#123; <span class="comment">//没有获得锁</span></span><br><span class="line">                    System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;没有获得锁&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">lock</span>.isHeldByCurrentThread())&#123;</span><br><span class="line">                    <span class="keyword">lock</span>.unlock();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        TimeLock timeLock = <span class="keyword">new</span> TimeLock();</span><br><span class="line">        Thread t1 = <span class="keyword">new</span> Thread(timeLock);</span><br><span class="line">        Thread t2 = <span class="keyword">new</span> Thread(timeLock);</span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>tryLock()仅在调用时锁定未被其他线程持有的锁,如果调用方法时,锁对象对其他线程持有,则放弃。调用方法尝试获得没,如果该锁没有被其他线程占用则返回 true 表示锁定成功; 如果锁被其他线程占用 则返回 false,不等待.<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test08 &#123;</span><br><span class="line">    static <span class="keyword">class</span> Service&#123;</span><br><span class="line">        <span class="keyword">private</span> ReentrantLock lock = <span class="keyword">new</span> <span class="constructor">ReentrantLock()</span>;</span><br><span class="line">        public void service<span class="constructor">Method()</span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (lock.<span class="keyword">try</span><span class="constructor">Lock()</span>)&#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;获得锁定&quot;</span>);</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">3000</span>); <span class="comment">//模拟执行任务的时长</span></span><br><span class="line">                &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;没有获得锁定 &quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125; finally &#123;</span><br><span class="line">                <span class="keyword">if</span> (lock.is<span class="constructor">HeldByCurrentThread()</span>)&#123;</span><br><span class="line">                    lock.unlock<span class="literal">()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) throws InterruptedException &#123;</span><br><span class="line">        Service service = <span class="keyword">new</span> <span class="constructor">Service()</span>;</span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                service.service<span class="constructor">Method()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        Thread t1 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>)</span>;</span><br><span class="line">        t1.start<span class="literal">()</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">50</span>); <span class="comment">//睡眠 50 毫秒,确保 t1 线程锁定</span></span><br><span class="line">        Thread t2 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>)</span>;</span><br><span class="line">        t2.start<span class="literal">()</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="newCondition-方法"><a href="#newCondition-方法" class="headerlink" title="newCondition()方法"></a><strong>newCondition()方法</strong></h2><p>关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实 现等待/通知模式. Lock 锁的 newContition()方法返回 Condition 对 象,Condition 类也可以实现等待/通知模式. 使用 notify()通知时, JVM 会随机唤醒某个等待的线程。</p>
<p>使用 Condition 类可以进行选择性通知. Condition 比较常用的两个方法: await()会使当前线程等待,同时会释放锁,当其他线程调用 signal() 时,线程会重新获得锁并继续执行</p>
<p>signal()用于唤醒一个等待的线程</p>
<p>注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相 关的 Lock 锁。 调用 await()后线程会释放这个锁,在 singal()调用后会从 当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒 的线程尝试 获得锁, 一旦获得锁成功就继续执行。</p>
<h2 id="公平锁与非公平锁"><a href="#公平锁与非公平锁" class="headerlink" title="公平锁与非公平锁"></a><strong>公平锁与非公平锁</strong></h2><p>大多数情况下,锁的申请都是非公平的. 如果线程1与线程2都在请求 锁 A, 当锁 A 可用时, 系统只是会从阻塞队列中随机的选择一个线程, 不能保证其公平性</p>
<p>公平的锁会按照时间先后顺序,保证先到先得, 公平锁的这一特点不 会出现线程饥饿现象</p>
<p>synchronized 内部锁就是非公平的. ReentrantLock 重入锁提供了一个 构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递 true 可以把该锁设置为公平锁. 公平锁看起来很公平,但是要实现公 平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也 低. 因此默认情况下锁是非公平的. 不是特别的需求,一般不使用公平锁</p>
<ol>
<li><p>int getHoldCount() 返回当前线程调用 lock()方法的次数</p>
</li>
<li><p>int getQueueLength() 返回正等待获得锁的线程预估数</p>
</li>
<li><p>int getWaitQueueLength(Condition condition) 返回与 Condition 条件 相关的等待的线程预估数</p>
</li>
<li><p>boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否 在等待获得锁</p>
</li>
<li><p>boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁</p>
</li>
<li><p>boolean hasWaiters(Condition condition) 查询是否有线程正在等待指定的 Condition 条件</p>
</li>
<li><p>boolean isFair() 判断是否为公平锁</p>
</li>
<li><p>boolean isHeldByCurrentThread() 判断当前线程是否持有该锁</p>
</li>
<li><p>boolean isLocked() 查询当前锁是否被线程持有；</p>
</li>
</ol>
<figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> ReentrantLock <span class="keyword">lock</span> = <span class="keyword">new</span> ReentrantLock(); <span class="comment">//默认是非公平锁</span></span><br><span class="line"><span class="keyword">static</span> ReentrantLock <span class="keyword">lock</span> = <span class="keyword">new</span> ReentrantLock(<span class="literal">true</span>); <span class="comment">//定义公平锁</span></span><br></pre></td></tr></table></figure>
<h3 id="ReentrantReadWriteLock-读写锁"><a href="#ReentrantReadWriteLock-读写锁" class="headerlink" title="ReentrantReadWriteLock 读写锁"></a><strong>ReentrantReadWriteLock 读写锁</strong></h3><p>synchronized 内部锁与 ReentrantLock 锁都是独占锁(排它锁), 同一 时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。ReentrantReadWriteLock 读写锁是一种改进的排他锁,也可以称作 共享/排他锁。 允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。读写锁通过读锁与写锁来完成读写操作. 线程在读取共享数据前 必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的.线 程在修改共享数据前必须先持有写锁,写锁是排他的, 一个线程持有 写锁时其他线程无法获得相应的锁。读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程 都无法获得写锁, 保证线程在读取数据期间没有其他线程对数据进 行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变 量不被修改</p>
<p>读锁：</p>
<ol>
<li>获得条件：写锁未被任意线程持有</li>
<li>排他性：对读线程是共享的，对写线程是排他的</li>
<li>作用：允许多个读线程可以同时读取共享数据，保证在读共享数据时，没有其他线程对共享数据进行修改</li>
</ol>
<p>写锁：</p>
<ol>
<li>获得条件：该写锁未被其他线程持有，并且相应的读锁也未被其他线程持有</li>
<li>排他性：对读线程或者写线程都是排他的</li>
<li>保证写线程以独占的方式修改共享数据</li>
</ol>
<p>读写锁允许读读共享, 读写互斥,写写互斥</p>
<p>在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口 中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁. 该接口 的实现类是 ReentrantReadWriteLock</p>
<p>注意 readLock()与 writeLock()方法返回的锁对象是同一个锁的两个 不同的角色, 不是分别获得两个不同的锁. ReadWriteLock 接口实例可 以充当两个角色.读写锁的其他使用方法</p>
<figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">ReadWriteLock rwLock = new ReentrantReadWriteLock()<span class="regexp">//</span>定义读写锁</span><br><span class="line">Lock readLock = rwLock.readLock();<span class="regexp">//</span>获得读锁</span><br><span class="line">Lock writeLock = rwLock.writeLock();<span class="regexp">//</span>获得写锁</span><br><span class="line">readLock.lock(); <span class="regexp">//</span>申请读锁</span><br><span class="line"><span class="regexp">//</span>读数据</span><br><span class="line">try&#123;</span><br><span class="line">    读取共享数据 </span><br><span class="line">&#125;finally&#123;</span><br><span class="line">    readLock.unlock(); <span class="regexp">//</span>总是在 finally 子句中释放锁</span><br><span class="line">&#125;</span><br><span class="line"><span class="regexp">//</span>写数据</span><br><span class="line">writeLock.lock(); <span class="regexp">//</span>申请写锁</span><br><span class="line">try&#123;</span><br><span class="line">    更新修改共享数据 </span><br><span class="line">&#125;finally&#123; </span><br><span class="line">    writeLock.unlock(); <span class="regexp">//</span>总是在 finally 子句中释放锁 </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>读读共享</strong></p>
<p>ReadWriteLock 读写锁可以实现多个线程同时读取共享数据,即读 读共享,可以提高程序的读取数据的效率<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test01</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Service</span> &#123;</span><br><span class="line">        <span class="comment">//定义读写锁</span></span><br><span class="line">        ReadWriteLock readWriteLock = <span class="keyword">new</span> ReentrantReadWriteLock();</span><br><span class="line">        <span class="comment">//定义方法读取数据</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">read</span>()</span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                readWriteLock.readLock().<span class="keyword">lock</span>(); <span class="comment">//获得读锁</span></span><br><span class="line">                System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;获得读锁,开始读取数据的时间--&quot;</span> + System.currentTimeMillis());</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">3</span>); <span class="comment">//模拟读取数据用时</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                readWriteLock.readLock().unlock();<span class="comment">//释放读锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        Service service = <span class="keyword">new</span> Service();</span><br><span class="line">        <span class="comment">//创建 5 个线程,调用 read()方法</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                    service.read(); <span class="comment">//在线程中调用 read()读取数据</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;<span class="comment">//运行程序后,这多个 线程几乎可以同时获得锁读,执行 lock()后面的代码</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>写写互斥</strong></p>
<p>通过 ReadWriteLock 读写锁中的写锁,只允许有一个线程执行 lock() 后面的代码<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Test02</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Service</span>&#123;</span><br><span class="line">        <span class="comment">//先定义读写锁</span></span><br><span class="line">        ReadWriteLock readWriteLock = <span class="keyword">new</span> ReentrantReadWriteLock();</span><br><span class="line">        <span class="comment">//定义方法修改数据</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span>()</span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                readWriteLock.writeLock().<span class="keyword">lock</span>(); <span class="comment">//申请获得写锁</span></span><br><span class="line">                System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;获得写锁,开始修改 数据的时间--&quot;</span> + System.currentTimeMillis());</span><br><span class="line">                Thread.sleep(<span class="number">3000</span>); <span class="comment">//模拟修改数据的用时</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">                System.<span class="keyword">out</span>.println(Thread.currentThread().getName() + <span class="string">&quot;读取数据完毕时的 时间==&quot;</span> + System.currentTimeMillis());</span><br><span class="line">                readWriteLock.writeLock().unlock(); <span class="comment">//释放写锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>)</span> &#123;</span><br><span class="line">        Service service = <span class="keyword">new</span> Service();</span><br><span class="line">        <span class="comment">//创建 5 个线程修改数据</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">            <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                @Override</span><br><span class="line">                <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span> &#123;</span><br><span class="line">                    service.write(); <span class="comment">//调用修改数据的方法</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).start();</span><br><span class="line">        &#125;<span class="comment">//从执行结果来看,同一时间只有一个线程获得写锁</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>读写互斥</strong></p>
<p>写锁是独占锁,是排他锁,读线程与写线程也是互斥的</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//一个线程获得读锁时,写线程等待; 一个线程获得写锁时,其他线程等待</span></span><br><span class="line">public <span class="keyword">class</span> Test03 &#123;</span><br><span class="line">    static <span class="keyword">class</span> Service &#123;</span><br><span class="line">        <span class="comment">//先定义读写锁</span></span><br><span class="line">        ReadWriteLock readWriteLock = <span class="keyword">new</span> <span class="constructor">ReentrantReadWriteLock()</span>;</span><br><span class="line">        Lock readLock = readWriteLock.read<span class="constructor">Lock()</span>; <span class="comment">//获得读锁</span></span><br><span class="line">        Lock writeLock = readWriteLock.write<span class="constructor">Lock()</span>; <span class="comment">//获得写锁</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 定义方法读取数据</span></span><br><span class="line">        public void read<span class="literal">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                readLock.lock<span class="literal">()</span>; <span class="comment">//申请获得读锁</span></span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;获得读锁,开始读取 数据的时间--&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">3000</span>); <span class="comment">//模拟读取数据的用时</span></span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125; finally &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;读取数据完毕时的 时间==&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                readLock.unlock<span class="literal">()</span>; <span class="comment">//释放读锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//定义方法修改数据</span></span><br><span class="line">        public void write<span class="literal">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                writeLock.lock<span class="literal">()</span>; <span class="comment">//申请获得写锁</span></span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;获得写锁,开始修改数据的时间--&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">3000</span>); <span class="comment">//模拟修改数据的用时</span></span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125; finally &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;修改数据完毕时的 时间==&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                writeLock.unlock<span class="literal">()</span>; <span class="comment">//释放写锁</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        Service service = <span class="keyword">new</span> <span class="constructor">Service()</span>;</span><br><span class="line">        <span class="comment">//定义一个线程读数据</span></span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">new</span> Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                service.read<span class="literal">()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start<span class="literal">()</span>;</span><br><span class="line">        <span class="comment">//定义一个线程写数据</span></span><br><span class="line">        <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">new</span> Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                service.write<span class="literal">()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).start<span class="literal">()</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="线程管理"><a href="#线程管理" class="headerlink" title="线程管理"></a><strong>线程管理</strong></h1><h2 id="线程组"><a href="#线程组" class="headerlink" title="线程组"></a><strong>线程组</strong></h2><p>Thread 类有几个构造方法允许在创建线程时指定线程组,如果在创建线程时没有指定线程组则该线程就属于父线程所在的线程组. JVM 在创建 main 线程时会为它指定一个线程组,因此每个 Java 线程都 有一个线程组与之关联, 可以调用线程的 getThreadGroup()方法返回线程组.</p>
<p>线程组开始是出于安全的考虑设计用来区分不同的 Applet,然而 ThreadGroup 并未实现这一目标，<strong>在新开发的系统中,已经不常用线程 组, 现在一般会将一组相关的线程存入一个数组或一个集合中,如果 仅仅是用来区分线程时,可以使用线程名称来区分, 多数情况下,可以忽略线程组。</strong></p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test01 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        <span class="comment">// 1) 返回当前 main 线程的线程组</span></span><br><span class="line">        ThreadGroup mainGroup = <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">ThreadGroup()</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(mainGroup);</span><br><span class="line">        <span class="comment">//2) 定义线程组,如果不指定所属线程组,则自动归属当前线程所属的线程组中</span></span><br><span class="line">        ThreadGroup group1 = <span class="keyword">new</span> <span class="constructor">ThreadGroup(<span class="string">&quot;group1&quot;</span>)</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(group1);</span><br><span class="line">        <span class="comment">//3)定义线程组, 同时指定父线程组</span></span><br><span class="line">        ThreadGroup group2 = <span class="keyword">new</span> <span class="constructor">ThreadGroup(<span class="params">mainGroup</span>, <span class="string">&quot;group2&quot;</span>)</span>;</span><br><span class="line">        <span class="comment">//现在 group1 与 group2 都是 maingroup 线程组中的子线程组, 调用线程组的 getParent()方法返回父线程组</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( group1.get<span class="constructor">Parent()</span><span class="operator"> == </span>mainGroup);<span class="comment">//true</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( group2.get<span class="constructor">Parent()</span><span class="operator"> == </span>mainGroup);<span class="comment">//true</span></span><br><span class="line">        <span class="comment">//4) 在创建线程时指定所属线程组</span></span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//在创建线程时,如果没有指定线程组,则默认线程归属到父 线程的线程组中</span></span><br><span class="line">        <span class="comment">//在 main 线程中创建了 t1 线程,称 main 线程为父线程,t1 线 程为子线程, t1 没有指定线程组则 t1 线程就归属到父线程 main 线程 的线程组中</span></span><br><span class="line">        Thread t1 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>, <span class="string">&quot;t1&quot;</span>)</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println( t1 ); <span class="comment">//Thread[t1,5,main], t1 的线程组 是 main 线程组</span></span><br><span class="line">        <span class="comment">//创建线程时,可以指定线程所属线程组</span></span><br><span class="line">        Thread t2 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">group1</span>, <span class="params">r</span>, <span class="string">&quot;t2&quot;</span>)</span>;</span><br><span class="line">        Thread t3 = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">group2</span>, <span class="params">r</span>, <span class="string">&quot;t3&quot;</span>)</span>;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(t2);</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(t3);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>线程组的基本操作</strong></p>
<ol>
<li>activeCount() 返回当前线程组及子线程组中活动线程的数量(近似值)</li>
<li>activeGroupCount() 返回当前线程组及子线程组中活动线程组的数量 (近似值)</li>
<li>int enumerate(Thread[] list) 将当前线程组中的活动线程复制到参数数组中</li>
<li>enumerate(ThreadGroup[] list) 将当前线程组中的活动线程组复制到 参数数组中</li>
<li>getMaxPriority() 返回线程组的最大优先级,默认是 10</li>
<li>getName() 返回线程组的名称</li>
<li>getParent() 返回父线程组</li>
<li>interrupt() 中断线程组中所有的线程</li>
<li>isDaemon() 判断当前线程组是否为守护线程组</li>
<li>list() 将当前线程组中的活动线程打印出来</li>
<li>parentOf(ThreadGroup g) 判断当前线程组是否为参数线程组的父线程组</li>
<li>setDaemon(boolean daemon) 设置线程组为守护线程组</li>
</ol>
<h2 id="捕获线程的执行异常"><a href="#捕获线程的执行异常" class="headerlink" title="捕获线程的执行异常"></a><strong>捕获线程的执行异常</strong></h2><p>在线程的run方法中,如果有受检异常必须进行捕获处理,如果想要 获 得 run() 方 法 中 出 现 的 运 行 时 异 常 信 息 , 可 以 通 过 回 调 UncaughtExceptionHandler 接口获得哪个线程出现了运行时异常</p>
<p>在 Thread 类中有关处理运行异常的方法有:</p>
<ol>
<li>getDefaultUncaughtExceptionHandler() 获 得 全 局 的 ( 默 认 的)UncaughtExceptionHandler</li>
<li>getUncaughtExceptionHandler() 获 得 当 前 线 程 的 UncaughtExceptionHandler</li>
<li>setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHan dler eh) 设置全局的 UncaughtExceptionHandler</li>
<li>setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置当前线程的 UncaughtExceptionHandler</li>
</ol>
<p>当 线 程 运 行 过 程 中 出 现 异 常 ,JVM 会 调 用 Thread 类 的 dispatchUncaughtException(Throwable e) 方 法 , 该 方 法 会 调 用 getUncaughtExceptionHandler().uncaughtException(this, e); 如 果 想 要 获 得 线 程 中 出 现 异 常 的 信 息 , 就 需 要 设 置 线 程 的 UncaughtExceptionHandler<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">main</span>(<span class="params"><span class="built_in">String</span>[] args</span>) &#123;</span><br><span class="line">        <span class="comment">//1)设置线程全局的回调接口</span></span><br><span class="line">        <span class="title class_">Thread</span>.<span class="title function_">setDefaultUncaughtExceptionHandler</span>(<span class="keyword">new</span> <span class="title class_">Thread</span>.<span class="title class_">UncaughtExceptionHandler</span>()&#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">uncaughtException</span>(<span class="params">Thread t, Throwable e</span>) &#123; <span class="comment">//t 参数接收发生异常的线程, e 就是该线程中的异常</span></span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(t.<span class="title function_">getName</span>() + <span class="string">&quot;线程产生了异常: &quot;</span> + e.<span class="title function_">getMessage</span>());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="title class_">Thread</span> t1 = <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="title class_">Thread</span>.<span class="title function_">currentThread</span>().<span class="title function_">getName</span>() + <span class="string">&quot;开始运行&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="title class_">Thread</span>.<span class="title function_">sleep</span>(<span class="number">2000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (<span class="title class_">InterruptedException</span> e) &#123;</span><br><span class="line">                    <span class="comment">//线程中的受检异常必须捕获处理</span></span><br><span class="line">                    e.<span class="title function_">printStackTrace</span>();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(<span class="number">12</span> / <span class="number">0</span> ); <span class="comment">//会产生算术异常</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        t1.<span class="title function_">start</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span> <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">                <span class="title class_">String</span> txt = <span class="literal">null</span>;</span><br><span class="line">                <span class="title class_">System</span>.<span class="property">out</span>.<span class="title function_">println</span>(txt.<span class="title function_">length</span>()); <span class="comment">//会产生空指针异常</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;).<span class="title function_">start</span>();</span><br><span class="line">        <span class="comment">/* 在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法</span></span><br><span class="line"><span class="comment">        如果线程产生了异常, JVM 会调用 dispatchUncaughtException()方法,在该方法中调用了 getUncaughtExceptionHandler().uncaughtException(this, e);</span></span><br><span class="line"><span class="comment">        如果当前线程设置了 UncaughtExceptionHandler 回调接口就直接调用它自己的 uncaughtException 方法,</span></span><br><span class="line"><span class="comment">        如果没有设置则调用当前线程所在线程组 UncaughtExceptionHandler 回调接口的 uncaughtException 方法,</span></span><br><span class="line"><span class="comment">        如果线程组也没有设置回调接口,则直接把异常的栈信息定向到 System.err 中</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>注入 Hook 钩子线程</strong></p>
<p>现在很多软件包括 MySQL, Zookeeper, kafka 等都存在 Hook 线程的 校验机制, 目的是校验进程是否已启动,防止重复启动程序。</p>
<p>Hook 线程也称为钩子线程, 当 JVM 退出的时候会执行 Hook 线程. 经常在程序启动时创建一个.lock 文件, 用.lock 文件校验程序是否启动,在程序退出(JVM 退出)时删除该.lock 文件, 在 Hook 线程中除了防 止重新启动进程外,还可以做资源释放, 尽量避免在 Hook 线程中进行复杂的操作。<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        <span class="comment">//1)注入 Hook 线程,在程序退出时删除.lock 文件</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Runtime</span>.</span></span>get<span class="constructor">Runtime()</span>.add<span class="constructor">ShutdownHook(<span class="params">new</span> Thread()</span>&#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot;JVM 退出,会启动当前 Hook 线程,在 Hook 线程中删除.lock 文件&quot;</span>);</span><br><span class="line">                get<span class="constructor">LockFile()</span>.<span class="keyword">to</span><span class="constructor">File()</span>.delete<span class="literal">()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">//2)程序运行时,检查 lock 文件是否存在,如果 lock 文件存在,则抛出异常</span></span><br><span class="line">        <span class="keyword">if</span> ( get<span class="constructor">LockFile()</span>.<span class="keyword">to</span><span class="constructor">File()</span>.exists<span class="literal">()</span>)&#123;</span><br><span class="line">            throw <span class="keyword">new</span> <span class="constructor">RuntimeException(<span class="string">&quot;程序已启动&quot;</span>)</span>;</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123; <span class="comment">//文件不存在,说明程序是第一次启动,创建 lock 文件</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                get<span class="constructor">LockFile()</span>.<span class="keyword">to</span><span class="constructor">File()</span>.create<span class="constructor">NewFile()</span>;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot;程序在启动时创建了 lock 文件&quot;</span>);</span><br><span class="line">            &#125; catch (IOException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//模拟程序运行</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot;程序正在运行&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">TimeUnit</span>.</span><span class="module"><span class="identifier">SECONDS</span>.</span></span>sleep(<span class="number">1</span>);</span><br><span class="line">            &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">private</span> static Path get<span class="constructor">LockFile()</span>&#123;</span><br><span class="line">        return <span class="module-access"><span class="module"><span class="identifier">Paths</span>.</span></span>get(<span class="string">&quot;&quot;</span>, <span class="string">&quot;tmp.lock&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h1 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a><strong>线程池</strong></h1><h2 id="什么是线程池"><a href="#什么是线程池" class="headerlink" title="什么是线程池"></a><strong>什么是线程池</strong></h2><p>可以以 new Thread( () -&gt; { 线程执行的任务 }).start(); 这种形式开启一个线程. 当 run()方法运行结束,线程对象会被 GC 释放。</p>
<p>在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时 ,反而会耗尽 CPU 资源. 如果不对线程进行控制与管理, 反而会影响程序的性能。</p>
<p>线程开销主要包括: 创建与启动线程的开销; 线程销毁开销; 线程调度的开销; 线程数量受限 CPU 处理器数量</p>
<p>线程池就是有效使用线程的一种常用方式。线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池, 线程池将这些任务缓存在工作队列中, 线程池中的工作线程不断地从队列中取出任务并执行。</p>
<img src="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/%E7%BA%BF%E7%A8%8B%E6%B1%A0.png" class title="线程池">
<h2 id="JDK-对线程池的支持"><a href="#JDK-对线程池的支持" class="headerlink" title="JDK 对线程池的支持"></a><strong>JDK 对线程池的支持</strong></h2><p>JDK 提供了一套 Executor 框架,可以帮助开发人员有效的使用线程池</p>
<img src="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/Excutor%E6%A1%86%E6%9E%B6.png" class title="Excutor框架">
<p>线程池的基本使用:<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test01 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        <span class="comment">//创建有 5 个线程大小的线程池</span></span><br><span class="line">        ExecutorService fixedThreadPool = <span class="module-access"><span class="module"><span class="identifier">Executors</span>.</span></span><span class="keyword">new</span><span class="constructor">FixedThreadPool(5)</span>;</span><br><span class="line">        <span class="comment">//向线程池中提交 18 个任务,这 18 个任务存储到线程池的阻塞队列中, 线程池中这 5 个线程就从阻塞队列中取任务执行</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">18</span>; i++)&#123;</span><br><span class="line">            fixedThreadPool.execute(<span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">                @Override</span><br><span class="line">                public void run<span class="literal">()</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Id()</span> + <span class="string">&quot; 编号的任务在执行任务,开始时间: &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">3000</span>); <span class="comment">//模拟任务执行时长</span></span><br><span class="line">                    &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                        e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>线程池的计划任务:能够在周期时间或者规定延迟后循环执行某项任务<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test02 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        <span class="comment">//创建一个有调度功能的线程池</span></span><br><span class="line">        ScheduledExecutorService scheduledExecutorService = <span class="module-access"><span class="module"><span class="identifier">Executors</span>.</span></span><span class="keyword">new</span><span class="constructor">ScheduledThreadPool(10)</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//在延迟 2 秒后执行任务, schedule( Runnable 任务, 延迟时长, 时间单位)</span></span><br><span class="line">        scheduledExecutorService.schedule(<span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Id()</span> + <span class="string">&quot; -- &quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="number">4</span>, TimeUnit.SECONDS);</span><br><span class="line">        <span class="comment">//以固定的频率执行任务,开启任务的时间是固定的, 在 3 秒后执行任务,以后每隔 5 秒重新执行一次</span></span><br><span class="line"></span><br><span class="line">        scheduledExecutorService.schedule<span class="constructor">AtFixedRate(<span class="params">new</span> Runnable()</span> &#123;</span><br><span class="line">            @Override public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Id()</span> + <span class="string">&quot;----在固定频率开启任务---&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">TimeUnit</span>.</span><span class="module"><span class="identifier">SECONDS</span>.</span></span>sleep(<span class="number">3</span>); <span class="comment">//睡眠模拟任务执行时间 ,如果任务执 行时长超过了时间间隔,则任务完成后立即开启下个任务</span></span><br><span class="line">                &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                    e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            &#125;, <span class="number">3</span>, <span class="number">2</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束 后的 2 秒再次开启新的任务</span></span><br><span class="line"><span class="comment">//        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() &#123;</span></span><br><span class="line"><span class="comment">//            @Override</span></span><br><span class="line"><span class="comment">//            public void run() &#123;</span></span><br><span class="line"><span class="comment">//                System.out.println(Thread.currentThread().getId() + &quot;----在固定频率开启任务---&quot; + System.currentTimeMillis());</span></span><br><span class="line"><span class="comment">//                try &#123;</span></span><br><span class="line"><span class="comment">//                    TimeUnit.SECONDS.sleep(3); //睡眠模拟任务执行时间 ,如果任务执 行时长超过了时间间隔,则任务完成后立即开启下个任务</span></span><br><span class="line"><span class="comment">//                &#125; catch (InterruptedException e) &#123;</span></span><br><span class="line"><span class="comment">//                    e.printStackTrace();</span></span><br><span class="line"><span class="comment">//                &#125;</span></span><br><span class="line"><span class="comment">//            &#125;</span></span><br><span class="line"><span class="comment">//        &#125;, 3, 2, TimeUnit.SECONDS);</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="核心线程池的底层实现"><a href="#核心线程池的底层实现" class="headerlink" title="核心线程池的底层实现"></a><strong>核心线程池的底层实现</strong></h3><p>查 看 Executors 工 具 类 中 newCachedThreadPool(), newSingleThreadExcecutor(), newFixedThreadPool()源码</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="keyword">new</span><span class="type">CachedThreadPool</span>() &#123; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="type">ThreadPoolExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">60</span>L,</span><br><span class="line">    TimeUnit.SECONDS, <span class="keyword">new</span> <span class="type">SynchronousQueue</span>&lt;Runnable&gt;()); </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//该线程池在极端情况下,每次提交新的任务都会创建新的线程执行. 适合用来执行大量 耗时短并且提交频繁的任务</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="keyword">new</span><span class="type">FixedThreadPool</span>(int nThreads) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="type">ThreadPoolExecutor</span>(nThreads, nThreads, <span class="number">0</span>L, </span><br><span class="line">            TimeUnit.MILLISECONDS, <span class="keyword">new</span> <span class="type">LinkedBlockingQueue</span>&lt;Runnable&gt;());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="keyword">new</span><span class="type">SingleThreadExecutor</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="type">FinalizableDelegatedExecutorService</span> (<span class="keyword">new</span> <span class="type">ThreadPoolExecutor</span>(<span class="number">1</span>, <span class="number">1</span>,<span class="number">0</span>L,</span><br><span class="line">            TimeUnit.MILLISECONDS, <span class="keyword">new</span> <span class="type">LinkedBlockingQueue</span>&lt;Runnable&gt;()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Excutors 工 具 类 中 返 回 线 程 池 的 方 法 底 层 都 使 用 了 ThreadPoolExecutor 线程池,这些方法都是 ThreadPoolExecutor 线程池的封装。</p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//ThreadPoolExecutor 的构造方法: </span></span><br><span class="line"><span class="keyword">public</span> ThreadPoolExecutor(<span class="keyword">int</span> corePoolSize,</span><br><span class="line">                            <span class="keyword">int</span> maximumPoolSize,</span><br><span class="line">                            <span class="keyword">long</span> keepAliveTime,</span><br><span class="line">                            TimeUnit unit,</span><br><span class="line">                            BlockingQueue&lt;Runnable&gt; workQueue,</span><br><span class="line">                            ThreadFactory threadFactory,</span><br><span class="line">                            RejectedExecutionHandler <span class="keyword">handler</span>)</span><br></pre></td></tr></table></figure>
<p>各个参数的含义：<br>参考前面面试题笔记。</p>
<p>Executors 工具类提供的静态方法返回的线程池默认的拒绝策略是 AbortPolicy 抛出异常,如果内置的拒绝策略无法满足实际需求,可以扩展 RejectedExecutionHandler 接口<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test03 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) &#123;</span><br><span class="line">        <span class="comment">//定义任务</span></span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="built_in">int</span> num = <span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(5)</span>;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Id()</span> + <span class="string">&quot;--&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> + <span class="string">&quot;开始睡眠&quot;</span> + num + <span class="string">&quot;秒&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">TimeUnit</span>.</span><span class="module"><span class="identifier">SECONDS</span>.</span></span>sleep(num);</span><br><span class="line">                &#125; catch (InterruptedException e) &#123;</span><br><span class="line">                    e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//创建线程池, 自定义拒绝策略</span></span><br><span class="line">        ThreadPoolExecutor threadPoolExecutor = <span class="keyword">new</span> <span class="constructor">ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, <span class="params">new</span> LinkedBlockingQueue&lt;&gt;(10)</span>, <span class="module-access"><span class="module"><span class="identifier">Executors</span>.</span></span>default<span class="constructor">ThreadFactory()</span>, <span class="keyword">new</span> <span class="constructor">RejectedExecutionHandler()</span>&#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void rejected<span class="constructor">Execution(Runnable <span class="params">r</span>, ThreadPoolExecutor <span class="params">executor</span>)</span> &#123;</span><br><span class="line">                <span class="comment">//r 就是请求的任务, executor 就是当前线程池</span></span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(r + <span class="string">&quot; is discarding..&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">//向线程池提交若干任务</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; Integer.MAX_VALUE; i++) &#123;</span><br><span class="line">            threadPoolExecutor.submit(r);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="ThreadFactory"><a href="#ThreadFactory" class="headerlink" title="ThreadFactory"></a><strong>ThreadFactory</strong></h3><p>线程池中的线程从哪儿来的? 答案就是 ThreadFactory</p>
<p>ThreadFactory 是一个接口,只有一个用来创建线程的方法</p>
<p><code>Thread newThread(Runnable r);</code></p>
<p>当线程池中需要创建线程时就会调用该方法</p>
<p>自定义线程工厂<br><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test04 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) throws InterruptedException&#123;</span><br><span class="line">        <span class="comment">//定义任务</span></span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span> &#123;</span><br><span class="line">                <span class="built_in">int</span> num = <span class="keyword">new</span> <span class="constructor">Random()</span>.next<span class="constructor">Int(10)</span>;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Id()</span> + <span class="string">&quot;--&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span> + <span class="string">&quot;开始睡眠:&quot;</span> + num + <span class="string">&quot;秒&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">TimeUnit</span>.</span><span class="module"><span class="identifier">SECONDS</span>.</span></span>sleep(num);</span><br><span class="line">                &#125;catch (InterruptedException e)&#123;</span><br><span class="line">                    e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//创建线程池, 使用自定义线程工厂, 采用默认的拒绝策略是抛出异常</span></span><br><span class="line">        ExecutorService executorService = <span class="keyword">new</span> <span class="constructor">ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, <span class="params">new</span> SynchronousQueue&lt;&gt;()</span>, <span class="keyword">new</span> <span class="constructor">ThreadFactory()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public Thread <span class="keyword">new</span><span class="constructor">Thread(Runnable <span class="params">r</span>)</span> &#123;</span><br><span class="line">                <span class="comment">//根据参数 r 接收的任务,创建一个线程</span></span><br><span class="line">                Thread t = <span class="keyword">new</span> <span class="constructor">Thread(<span class="params">r</span>)</span>;</span><br><span class="line">                t.set<span class="constructor">Daemon(<span class="params">true</span>)</span>; <span class="comment">//设置为守护线程, 当主线程运行结束,线程池中的 线程会自动退出</span></span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot;创建了线程: &quot;</span> + t);</span><br><span class="line">                return t ;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">//提交 5 个任务, 当给当前线程池提交的任务超过 5 个时,线程池默认抛出异常</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">            executorService.submit(r);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//主线程睡眠</span></span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">10000</span>);</span><br><span class="line">        <span class="comment">//主线程睡眠超时, 主线程结束, 线程池中的线程会自动退出</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="监控线程池"><a href="#监控线程池" class="headerlink" title="监控线程池"></a><strong>监控线程池</strong></h3><p>ThreadPoolExecutor 提供了一组方法用于监控线程池</p>
<ol>
<li><p>int getActiveCount() 获得线程池中当前活动线程的数量</p>
</li>
<li><p>long getCompletedTaskCount() 返回线程池完成任务的数量</p>
</li>
<li><p>int getCorePoolSize() 线程池中核心线程的数量</p>
</li>
<li><p>int getLargestPoolSize() 返回线程池曾经达到的线程的最大数</p>
</li>
<li><p>int getMaximumPoolSize() 返回线程池的最大容量</p>
</li>
<li><p>int getPoolSize() 当前线程池的大小</p>
</li>
<li><p>BlockingQueue<Runnable> getQueue() 返回阻塞队列</Runnable></p>
</li>
<li><p>long getTaskCount() 返回线程池收到的任务总数</p>
<figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> Test05 &#123;</span><br><span class="line">    public static void main(String<span class="literal">[]</span> args) throws InterruptedException&#123;</span><br><span class="line">        <span class="comment">//先定义任务</span></span><br><span class="line">        Runnable r = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line">            @Override</span><br><span class="line">            public void run<span class="literal">()</span>&#123;</span><br><span class="line">                <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>current<span class="constructor">Thread()</span>.get<span class="constructor">Name()</span> + <span class="string">&quot;编号的线程开始执行：&quot;</span> + <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>current<span class="constructor">TimeMillis()</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">10000</span>);<span class="comment">//睡眠20秒，模拟任务执行时长</span></span><br><span class="line">                &#125;catch (InterruptedException e)&#123;</span><br><span class="line">                    e.print<span class="constructor">StackTrace()</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        <span class="comment">//定义线程池</span></span><br><span class="line">        ThreadPoolExecutor poolExecutor = <span class="keyword">new</span> <span class="constructor">ThreadPoolExecutor(2, 5, 0, TimeUnit.SECONDS, <span class="params">new</span> ArrayBlockingQueue&lt;&gt;(5)</span>, <span class="module-access"><span class="module"><span class="identifier">Executors</span>.</span></span>default<span class="constructor">ThreadFactory()</span>, <span class="keyword">new</span> ThreadPoolExecutor.<span class="constructor">DiscardPolicy()</span>);</span><br><span class="line">        <span class="comment">//向线程池提交 30 个任务</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; <span class="number">30</span>; i++) &#123;</span><br><span class="line">            poolExecutor.submit(r);</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot; 当 前 线 程 池 核 心 线 程 数 量 : &quot;</span> + poolExecutor.get<span class="constructor">CorePoolSize()</span> + <span class="string">&quot;, 最大线程数:&quot;</span> + poolExecutor.get<span class="constructor">MaximumPoolSize()</span> + <span class="string">&quot;,当 前 线 程 池 大 小 :&quot;</span> + poolExecutor.get<span class="constructor">PoolSize()</span> + <span class="string">&quot;, 活 动 线 程 数 量 :&quot;</span> + poolExecutor.get<span class="constructor">ActiveCount()</span>+ <span class="string">&quot;,收到任务数量:&quot;</span> + poolExecutor.get<span class="constructor">TaskCount()</span> + <span class="string">&quot;,完成任务 数 : &quot;</span> + poolExecutor.get<span class="constructor">CompletedTaskCount()</span> + <span class="string">&quot;, 等 待 任 务 数 :&quot;</span> + poolExecutor.get<span class="constructor">Queue()</span>.size<span class="literal">()</span>) ;</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">TimeUnit</span>.</span><span class="module"><span class="identifier">MILLISECONDS</span>.</span></span>sleep(<span class="number">500</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot;-----------------------------------------------&quot;</span>);</span><br><span class="line">        <span class="keyword">while</span> ( poolExecutor.get<span class="constructor">ActiveCount()</span> &gt;= <span class="number">0</span> )&#123;</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">System</span>.</span></span>out.println(<span class="string">&quot; 当 前 线 程 池 核 心 线 程 数 量 : &quot;</span> + poolExecutor.get<span class="constructor">CorePoolSize()</span> + <span class="string">&quot;, 最大线程数:&quot;</span> + poolExecutor.get<span class="constructor">MaximumPoolSize()</span> + <span class="string">&quot;,当前 线 程 池 大 小 :&quot;</span> + poolExecutor.get<span class="constructor">PoolSize()</span> + <span class="string">&quot;, 活 动 线 程 数 量 :&quot;</span> + poolExecutor.get<span class="constructor">ActiveCount()</span>+ <span class="string">&quot;,收到任务数量:&quot;</span> + poolExecutor.get<span class="constructor">TaskCount()</span> + <span class="string">&quot;,完成任务 数 : &quot;</span> + poolExecutor.get<span class="constructor">CompletedTaskCount()</span> + <span class="string">&quot;, 等 待 任 务 数 :&quot;</span> + poolExecutor.get<span class="constructor">Queue()</span>.size<span class="literal">()</span>);</span><br><span class="line">            <span class="module-access"><span class="module"><span class="identifier">Thread</span>.</span></span>sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="扩展线程池"><a href="#扩展线程池" class="headerlink" title="扩展线程池"></a><strong>扩展线程池</strong></h3></li>
</ol>
<p>有时需要对线程池进行扩展,如在监控每个任务的开始和结束时 间,或者自定义一些其他增强的功能。</p>
<p>ThreadPoolExecutor 线程池提供了两个方法:</p>
<ol>
<li><p>protected void afterExecute(Runnable r, Throwable t)</p>
</li>
<li><p>protected void beforeExecute(Thread t, Runnable r)</p>
</li>
</ol>
<p>在线程池执行某个任务前会调用 beforeExecute()方法,在任务结束 后(任务异常退出)会执行 afterExecute()方法</p>
<p>查看 ThreadPoolExecutor 源码,在该类中定义了一个内部类 Worker, ThreadPoolExecutor 线程池中的工作线程就是 Worker 类的实例, Worker 实例在执行时会调用 beforeExecute()与 afterExecute()方法</p>
<hr>
<p>暂时性小总结：</p>
<p>不管是Executors.newFixedThreadPool(5)方法还是Executors.newScheduledThreadPool(10)方法，创建出来的其实都是ThreadPoolExcutor类，这个<code>ThreadPoolExcutor</code>类就是一个线程池。而线程池的主要任务就是创建线程，接收任务。</p>
<p>先说创建线程，一般我们自己创建线程都是继承一个Thread类或者实现Runnable接口，然后重写里面的run()方法。但是线程池它是自己来创建线程的。那么它利用什么创建线程呢？答案是<code>ThreadFactory</code>。</p>
<p>ThreadFactory是一个接口，在Excutors类的内部类DefaultThreadFactory中实现了ThreadFactory接口。</p>
<p>ThreadFactory接口定义了一个newThread(Runnable r)方法，用来创建线程。</p>
<p>当我们创建一个线程池的时候，我们可以在其构造方法中传入ThreadFactory实现类，或者自己实现ThreadFactory接口中的newThread方法。</p>
<p>再来说一下接受任务，接受任务有两个方法execute()和submit()</p>
<ol>
<li><p>execute只能提交Runnable类型的任务，没有返回值，而submit既能提交Runnable类型任务也能提交Callable类型任务，返回Future类型。</p>
</li>
<li><p>execute方法提交的任务异常是直接抛出的，而submit方法是是捕获了异常的，当调用FutureTask的get方法时，才会抛出异常。</p>
</li>
</ol>
<hr>
<h3 id="优化线程池大小"><a href="#优化线程池大小" class="headerlink" title="优化线程池大小"></a><strong>优化线程池大小</strong></h3><p>线程池大小对系统性能是有一定影响的,过大或者过小都会无法 发挥最优的系统性能, 线程池大小不需要非常精确,只要避免极大或 者极小的情况即可, 一般来说,线程池大小需要考虑 CPU 数量,内存大 小等因素。<br>在<Java concurrency in practice>书中给出一个估算线程池大小的公式：</Java></p>
<p>线程池大小 = CPU数量 × 目标CPU的使用率 × (1 + 等待时间与计算时间的比)</p>
<h3 id="线程池死锁"><a href="#线程池死锁" class="headerlink" title="线程池死锁"></a><strong>线程池死锁</strong></h3><p>如果在线程池中执行的 任务 A 在执行过程中又向线程池提交了 任务 B, 任务 B 添加到了线程池的等待队列中，如果任务 A 的结束需 要等待任务 B 的执行结果。 就有可能会出现这种情况： 线程池中所有 的工作线程都处于等待任务处理结果,而这些任务在阻塞队列中等待执行， 线程池中没有可以对阻塞队列中的任务进行处理的线程，这种 等待会一直持续下去，从而造成死锁。</p>
<p>适合给线程池提交相互独立的任务，而不是彼此依赖的任务。 对于彼此依赖的任务，可以考虑分别提交给不同的线程池来执行。</p>
<h3 id="线程池中的异常处理"><a href="#线程池中的异常处理" class="headerlink" title="线程池中的异常处理"></a><strong>线程池中的异常处理</strong></h3><p>在使用 ThreadPoolExecutor 进行 submit 提交任务时，有的任务抛出了异常，但是线程池并没有进行提示，即线程池把任务中的异常给吃掉了，可以把 submit 提交改为 execute 执行，也可以对 ThreadPoolExecutor 线程池进行扩展。对提交的任务进行包装。</p>
<p>自定义线程池类,对 ThreadPoolExecutor 进行扩展(封装)：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test08</span> &#123;</span><br><span class="line">    <span class="comment">//自定义线程池类</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">TraceThreadPollExecutor</span> <span class="keyword">extends</span> <span class="title class_">ThreadPoolExecutor</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">TraceThreadPollExecutor</span><span class="params">(<span class="type">int</span> corePoolSize, <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                                       <span class="type">long</span> keepAliveTime, TimeUnit unit, BlockingQueue&lt;Runnable&gt; workQueue)</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//定义方法,对执行的任务进行包装,接收两个参数,第一个参数接收要执行的任务, 第二个参数是一个 Exception 异常</span></span><br><span class="line">        <span class="keyword">public</span> Runnable <span class="title function_">wrap</span><span class="params">(Runnable task, Exception exception)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        task.run();</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                        exception.printStackTrace();</span><br><span class="line">                        <span class="keyword">throw</span> e;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//重写 submit 方法</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> Future&lt;?&gt; submit(Runnable task) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.submit(wrap(task, <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;客户跟踪异常&quot;</span>)));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>.execute(wrap(command, <span class="keyword">new</span> <span class="title class_">Exception</span>(<span class="string">&quot;客户跟踪异常&quot;</span>)));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//定义类实现 Runnable 接口,用于计算两个数相除</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DivideTask</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> x;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> y;</span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">DivideTask</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.x = x;</span><br><span class="line">            <span class="built_in">this</span>.y = y;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot;计算:&quot;</span> + x + <span class="string">&quot; / &quot;</span> + y + <span class="string">&quot; = &quot;</span> + (x/y));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//创建线程池</span></span><br><span class="line">        <span class="comment">// ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.SECONDS, new SynchronousQueue&lt;&gt;());</span></span><br><span class="line">        <span class="comment">//使用自定义的线程池</span></span><br><span class="line">        <span class="type">ThreadPoolExecutor</span> <span class="variable">poolExecutor</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TraceThreadPollExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE, <span class="number">0</span>, TimeUnit.SECONDS, <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;&gt;());</span><br><span class="line">        <span class="comment">//向线程池中添加计算两个数相除的任务</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">            poolExecutor.submit(<span class="keyword">new</span> <span class="title class_">DivideTask</span>(<span class="number">10</span>, i));</span><br><span class="line">            <span class="comment">// poolExecutor.execute(new DivideTask(10, i));</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h3 id="ForkJoinPool-线程池"><a href="#ForkJoinPool-线程池" class="headerlink" title="ForkJoinPool 线程池"></a><strong>ForkJoinPool 线程池</strong></h3><p>“分而治之”是一个有效的处理大数据的方法，著名的 MapReduce 就是采用这种分而治之的思路。 简单点说，如果要处理的 1000 个数据， 但是我们不具备处理1000个数据的能力，可以只处理10个数据， 可以把这 1000 个数据分阶段处理 100 次，每次处理 10 个，把 100 次的处理结果进行合成，形成最后这 1000 个数据的处理结果。</p>
<p>把一个大任务调用 fork()方法分解为若干小的任务,把小任务的处理结果进行 join()合并为大任务的结果。</p>
<p>系统对 ForkJoinPool 线程池进行了优化，提交的任务数量与线程的数量不一定是一对一关系.在多数情况下，一个物理线程实际上需要处理多个逻辑任务。</p>
<img src="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/ForkJoinPool.png" class title="ForkJoinPool">
<p>ForkJoinPool 线程池中最常用的方法是: <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)向线程池提交一个 ForkJoinTask 任务。ForkJoinTask 任务支持 fork()分解与 join()等待的任务。ForkJoinTask 有两个重要的子类 :RecursiveAction 和 RecursiveTask ,它们的区别在于 RecursiveAction 任务没有返回值, RecursiveTask 任务可以带有返回值。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test09</span> &#123;</span><br><span class="line">    <span class="comment">//计算数列的和, 需要返回结果,可以定义任务继承 RecursiveTask</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">CountTask</span> <span class="keyword">extends</span> <span class="title class_">RecursiveTask</span>&lt;Long&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">THRESHOLD</span> <span class="operator">=</span> <span class="number">10000</span>; <span class="comment">//定义数据规模的阈值,允许计算 10000 个数内的和,超过该阈值的数列就需要分解</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TASKNUM</span> <span class="operator">=</span> <span class="number">100</span>; <span class="comment">//定义每次把大任务分解为 100 个小任务</span></span><br><span class="line">        <span class="keyword">private</span> <span class="type">long</span> start; <span class="comment">//计算数列的起始值</span></span><br><span class="line">        <span class="keyword">private</span> <span class="type">long</span> end; <span class="comment">//计算数列的结束值</span></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">CountTask</span><span class="params">(<span class="type">long</span> start, <span class="type">long</span> end)</span> &#123; <span class="built_in">this</span>.start = start; <span class="built_in">this</span>.end = end; &#125;</span><br><span class="line">        <span class="comment">//重写 RecursiveTask 类的 compute()方法,计算数列的结果</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> Long <span class="title function_">compute</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">//保存计算的结果</span></span><br><span class="line">            <span class="comment">// 判断任务是否需要继续分解,如果当前数列 end 与 start 范围的数超过阈值 THRESHOLD,就需要继续分解</span></span><br><span class="line">            <span class="keyword">if</span> (end - start &lt; THRESHOLD) &#123; <span class="comment">//小于阈值可以直接计算</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">long</span> <span class="variable">i</span> <span class="operator">=</span> start; i &lt;= end; i++) &#123;</span><br><span class="line">                    sum += i;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123; <span class="comment">//数列范围超过阈值,需要继续分解</span></span><br><span class="line">                <span class="comment">// 约定每次分解成 100 个小任务,计算每个任务的计算量</span></span><br><span class="line">                <span class="type">long</span> <span class="variable">step</span> <span class="operator">=</span> (start + end) / TASKNUM; <span class="comment">//start = 0 , end = 200000, step = 2000, 如果计算[0,200000]范围内数列的 和, 把该范围的数列分解为 100 个小任务,每个任务计算 2000 个数即可</span></span><br><span class="line">                <span class="comment">// 注意,如果任务划分的层次很深,即 THRESHOLD 阈值太小,每个任务的计 算量很小,层次划分就会很深,可能出现两种情况:</span></span><br><span class="line">                <span class="comment">// 一是系统内的线程数量会越积越多,导致性 能下降严重;</span></span><br><span class="line">                <span class="comment">// 二是分解次数过多,方法调用过多可能会导致栈溢出</span></span><br><span class="line">                <span class="comment">// 创建一个存储任务的集合</span></span><br><span class="line">                ArrayList&lt;CountTask&gt; subTaskList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">                <span class="type">long</span> <span class="variable">pos</span> <span class="operator">=</span> start; <span class="comment">//每个任务的起始位置</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; TASKNUM; i++) &#123;</span><br><span class="line">                    <span class="type">long</span> <span class="variable">lastOne</span> <span class="operator">=</span> pos + step; <span class="comment">//每个任务的结束位置</span></span><br><span class="line">                    <span class="comment">// 调整最后一个任务的结束位置</span></span><br><span class="line">                    <span class="keyword">if</span> (lastOne &gt; end) &#123;</span><br><span class="line">                        lastOne = end;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">//创建子任务</span></span><br><span class="line">                    <span class="type">CountTask</span> <span class="variable">task</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountTask</span>(pos, lastOne);</span><br><span class="line">                    <span class="comment">//把任务添加到集合中</span></span><br><span class="line">                    subTaskList.add(task);</span><br><span class="line">                    <span class="comment">//调用 for()提交子任务</span></span><br><span class="line">                    task.fork();</span><br><span class="line">                    <span class="comment">//调整下个任务的起始位置</span></span><br><span class="line">                    pos += step + <span class="number">1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//等待所有的子任务结束后,合并计算结果</span></span><br><span class="line">                <span class="keyword">for</span> (CountTask task : subTaskList) &#123;</span><br><span class="line">                    sum += task.join(); <span class="comment">//join()会一直等待子任务执行完毕返回 执行结果</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> sum;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">//创建 ForkJoinPool 线程池</span></span><br><span class="line">        <span class="type">ForkJoinPool</span> <span class="variable">forkJoinPool</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ForkJoinPool</span>();</span><br><span class="line">        <span class="comment">//创建一个大的任务</span></span><br><span class="line">        <span class="type">CountTask</span> <span class="variable">task</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountTask</span>(<span class="number">0L</span>, <span class="number">200000L</span>);</span><br><span class="line">        <span class="comment">//把大任务提交给线程池</span></span><br><span class="line">        ForkJoinTask&lt;Long&gt; result = forkJoinPool.submit(task);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Long</span> <span class="variable">res</span> <span class="operator">=</span> result.get(); <span class="comment">//调用任务的 get()方法返回结果</span></span><br><span class="line">            System.out.println(<span class="string">&quot;计算数列结果为:&quot;</span> + res);</span><br><span class="line">        &#125;<span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (ExecutionException e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//验证</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">s</span> <span class="operator">=</span> <span class="number">0L</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">long</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= <span class="number">200000</span> ; i++) &#123;</span><br><span class="line">            s += i;</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></T></T></T></p>
<h1 id="保障线程安全的设计技术"><a href="#保障线程安全的设计技术" class="headerlink" title="保障线程安全的设计技术"></a><strong>保障线程安全的设计技术</strong></h1><p>从面向对象设计的角度出发介绍几种保障线程安全的设计技术，这些技术可以使得我们在不必借助锁的情况下保障线程安全，避免锁可能导致的问题及开销。</p>
<h2 id="Java-运行时存储空间"><a href="#Java-运行时存储空间" class="headerlink" title="Java 运行时存储空间"></a><strong>Java 运行时存储空间</strong></h2><p>Java运行时(Java runtime)空间可以分为栈区,堆区与方法区(非堆空间)</p>
<p>栈空间(Stack Space)为线程的执行准备一段固定大小的存储空间，每个线程都有独立的线程栈空间，创建线程时就为线程分配栈空间。在线程栈中每调用一个方法就给方法分配一个栈帧，栈帧用于存储方法的局部变量，返回值等私有数据，即局部变量存储在栈空间中， 基本类型变量也是存储在栈空间中，引用类型变量值也是存储在栈空间中，引用的对象存储在堆中。 由于线程栈是相互独立的，一个线程不能访问另外一个线程的栈空间，因此线程对局部变量以及只能通过当前线程的局部变量才能访问的对象进行的操作具有固定的线程安全性。</p>
<p>堆空间(Heap Space)用于存储对象，是在 JVM 启动时分配的一段可以动态扩容的内存空间。创建对象时，在堆空间中给对象分配存储空间，实例变量就是存储在堆空间中的，堆空间是多个线程之间可以共享的空间，因此实例变量可以被多个线程共享。多个线程同时操作实例变量可能存在线程安全问题。</p>
<p>非堆空间(Non-Heap Space)用于存储常量，类的元数据等， 非堆空间也是在 JVM 启动时分配的一段可以动态扩容的存储空间。类的元数据包括静态变量，类有哪些方法及这些方法的元数据(方法名,参数,返回值等)。 非堆空间也是多个线程可以共享的， 因此访问非堆空间中的静态变量也可能存在线程安全问题</p>
<p>堆空间和非堆空间是线程可以共享的空间，即实例变量与静态变量是线程可以共享的，可能存在线程安全问题。 栈空间是线程私有的存储空间，局部变量存储在栈空间中，局部变量具有固定的线程安全性。</p>
<h3 id="无状态对象"><a href="#无状态对象" class="headerlink" title="无状态对象"></a><strong>无状态对象</strong></h3><p>对象就是数据及对数据操作的封装，对象所包含的数据称为对象的状态(State)，实例变量与静态变量称为状态变量。</p>
<p>如果一个类的同一个实例被多个线程共享并不会使这些线程存储共享的状态，那么该类的实例就称为无状态对象(Stateless Object)。 反之如果一个类的实例被多个线程共享会使这些线程存在共享状态，那么该类的实例称为有状态对象。实际上无状态对象就是不包含任何实例变量也不包含任何静态变量的对象(不包含数据)。</p>
<p>线程安全问题的前提是多个线程存在共享的数据，实现线程安全的一种办法就是避免在多个线程之间共享数据，使用无状态对象就是这种方法。</p>
<h3 id="不可变对象"><a href="#不可变对象" class="headerlink" title="不可变对象"></a><strong>不可变对象</strong></h3><p>不可变对象是指一经创建它的状态就保持不变的对象，不可变对象具有固有的线程安全性。当不可变对象现实实体的状态发生变化时，系统会创建一个新的不可变对象，就如 String字符串对象。一个不可变对象需要满足以下条件：</p>
<ol>
<li>类本身使用 final 修饰,防止通过创建子类来改变它的定义</li>
<li>所有的字段都是 final 修饰的,final 字段在创建对象时必须显示初始化,不能被修改</li>
<li>如果字段引用了其他状态可变的对象(集合,数组),则这些字段 必须是 private 私有的</li>
</ol>
<p>不可变对象主要的应用场景：</p>
<ol>
<li><p>被建模对象的状态变化不频繁</p>
</li>
<li><p>同时对一组相关数据进行写操作，可以应用不可变对象，既可以保障原子性也可以避免锁的使用</p>
</li>
<li><p>使用不可变对象作为安全可靠的Map键，HashMap键值对的存储位置与键的 hashCode()有关，如果键的内部状态发生了变化会导致键的哈希码不同，可能会影响键值对的存储位置。如果 HashMap 的键是一个不可变对象，则 hashCode()方法的返回值恒定，存储位置是固定的。</p>
</li>
</ol>
<h3 id="线程特有对象"><a href="#线程特有对象" class="headerlink" title="线程特有对象"></a><strong>线程特有对象</strong></h3><p>我们可以选择不共享非线程安全的对象，对于非线程安全的对象，每个线程都创建一个该对象的实例，各个线程访问各自创建的实例，一个线程不能访问另外一个线程创建的实例。 这种各个线程创建各自的实例，<strong>一个实例只能被一个线程访问的对象就称为线程特有对象</strong>。 线程特有对象既保障了对非线程安全对象的访问的线程安全，又避免了锁的开销。线程特有对象也具有固有的线程安全性。</p>
<p>ThreadLocal<T>类相当于线程访问其特有对象的代理，即各个线程通过 ThreadLocal 对象可以创建并访问各自的线程特有对象，泛型 T 指定了线程特有对象的类型。一个线程可以使用不同的 ThreadLocal 实例来创建并访问不同的线程特有对象。</T></p>
<img src="/wang-cheng/2022/04/10/%E5%A4%9A%E7%BA%BF%E7%A8%8B/ThreadLocal.png" class title="ThreadLocal">
<p>ThreadLocal 实例为每个访问它的线程都关联了一个该线程特有的 对象, ThreadLocal 实例都有当前线程与特有实例之间的一个关联。</p>
<h1 id="锁的优化及注意事项"><a href="#锁的优化及注意事项" class="headerlink" title="锁的优化及注意事项"></a><strong>锁的优化及注意事项</strong></h1><h2 id="有助于提高锁性能的几点建议"><a href="#有助于提高锁性能的几点建议" class="headerlink" title="有助于提高锁性能的几点建议"></a><strong>有助于提高锁性能的几点建议</strong></h2><h3 id="减少锁持有时间"><a href="#减少锁持有时间" class="headerlink" title="减少锁持有时间"></a><strong>减少锁持有时间</strong></h3><p>对于使用锁进行并发控制的应用程序来说,如果单个线程特有锁 的时间过长,会导致锁的竞争更加激烈,会影响系统的性能.在程序中 需要尽可能减少线程对锁的持有时间,如下面代码:<br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public synchronized void <span class="built_in">syncMethod</span>()&#123; </span><br><span class="line">    <span class="built_in">othercode1</span>(); <span class="built_in">mutexMethod</span>(); <span class="built_in">othercode</span>(); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>在syncMethod同步方法中,假设只有mutexMethod()方法是需要同步的，othercode1()方法与 othercode2()方法不需要进行同步。 如果othercode1 与 othercode2 这两个方法需要花费较长的 CPU 时间,在并发量较大的情况下,这种同步方案会导致等待线程的大量增加。 一个 较好的优化方案是,只在必要时进行同步,可以减少锁的持有时间,提高系统的吞吐量,如把上面的代码改为：<br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public void <span class="built_in">syncMethod</span>()&#123; </span><br><span class="line">    <span class="built_in">othercode1</span>(); </span><br><span class="line">    synchronized (this) &#123; </span><br><span class="line">        <span class="built_in">mutexMethod</span>(); </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">othercode</span>(); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>只对 mutexMethod()方法进行同步，这种减少锁持有时间有助于降低锁冲突的可能性，提升系统的并发能力。</p>
<h3 id="减小锁的粒度"><a href="#减小锁的粒度" class="headerlink" title="减小锁的粒度"></a><strong>减小锁的粒度</strong></h3><p>一个锁保护的共享数据的数量大小称为锁的粒度。 如果一个锁保护的共享数据的数量大就称该锁的粒度粗，否则称该锁的粒度细。锁的粒度过粗会导致线程在申请锁时需要进行不必要的等待。减少锁粒度是一种削弱多线程锁竞争的一种手段，可以提高系统的并发性。</p>
<p>在JDK7前，java.util.concurrent.ConcurrentHashMap类采用分段锁协议，可以提高程序的并发性。</p>
<h3 id="使用读写分离锁代替独占锁"><a href="#使用读写分离锁代替独占锁" class="headerlink" title="使用读写分离锁代替独占锁"></a><strong>使用读写分离锁代替独占锁</strong></h3><p>使用ReadWriteLock读写分离锁可以提高系统性能，使用读写分离锁也是减小锁粒度的一种特殊情况。第二条建议是能分割数据结构实现减小锁的粒度，那么读写锁是对系统功能点的分割。</p>
<p>在多数情况下都允许多个线程同时读，在写的使用采用独占锁，在读多写少的情况下，使用读写锁可以大大提高系统的并发能力。</p>
<h3 id="锁分离"><a href="#锁分离" class="headerlink" title="锁分离"></a><strong>锁分离</strong></h3><p>将读写锁的思想进一步延伸就是锁分离。读写锁是根据读写操作功能上的不同进行了锁分离。根据应用程序功能的特点，也可以对独占锁进行分离.如 java.util.concurrent.LinkedBlockingQueue 类中take()与put()方法分别从队头取数据,把数据添加到队尾。 虽然这两个方法都是对队列进行修改操作，由于操作的主体是链表，take()操作的是链表 的头部，put()操作的是链表的尾部，两者并不冲突。如果采用独占锁的话，这两个操作不能同时并发，在该类中就采用锁分离，take()取数据时有取锁， put()添加数据时有自己的添加锁，这样 take()与 put()相互独立实现了并发。</p>
<h3 id="粗锁化"><a href="#粗锁化" class="headerlink" title="粗锁化"></a><strong>粗锁化</strong></h3><p>为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短。但是凡事都有一个度，如果对同一个锁不断的进行请求，同步和释放，也会消耗系统资源.如：</p>
<figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">method1</span>(<span class="params"></span>)&#123; </span><br><span class="line">    <span class="title function_">synchronized</span>(<span class="params"> lock </span>)&#123; </span><br><span class="line">        同步代码块 <span class="number">1</span> </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">synchronized</span>(<span class="params"> lock </span>)&#123;</span><br><span class="line">        同步代码块 <span class="number">2</span> </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>JVM 在遇到一连串不断对同一个锁进行请求和释放操作时，会把所有的锁整合成对锁的一次请求，从而减少对锁的请求次数，这个操作叫锁的粗化，如上一段代码会整合为：<br><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">method1</span>(<span class="params"></span>)&#123; </span><br><span class="line">    <span class="title function_">synchronized</span>(<span class="params"> lock </span>)&#123; </span><br><span class="line">        同步代码块 <span class="number">1</span> </span><br><span class="line">        同步代码块 <span class="number">2</span> </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在开发过程中，也应该有意识的在合理的场合进行锁的粗化，尤其在循环体内请求锁时，如：<br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">for</span>(int i = <span class="number">0</span> ; i&lt; <span class="number">100</span>; i++)&#123; </span><br><span class="line">    <span class="built_in">synchronized</span>(lock)&#123;</span><br><span class="line"></span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>这种情况下，意味着每次循环都需要申请锁和释放锁，所以一种更合理的做法就是在循环外请求一次锁，如：<br><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">synchronized</span>( lock )&#123; </span><br><span class="line">    <span class="built_in">for</span>(int i = <span class="number">0</span> ; i&lt; <span class="number">100</span>; i++)&#123;&#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="JVM-对锁的优化"><a href="#JVM-对锁的优化" class="headerlink" title="JVM 对锁的优化"></a><strong>JVM 对锁的优化</strong></h2><h3 id="锁偏向"><a href="#锁偏向" class="headerlink" title="锁偏向"></a><strong>锁偏向</strong></h3><p>锁偏向是一种针对加锁操作的优化，如果一个线程获得了锁，那么锁就进入偏向模式，当这个线程再次请求锁时，无须再做任何同步操作，这样可以节省有关锁申请的时间，提高了程序的性能。</p>
<p>锁偏向在没有锁竞争的场合可以有较好的优化效果，对于锁竞争比较激烈的场景，效果不佳， 锁竞争激烈的情况下可能是每次都是不同的线程来请求锁，这时偏向模式失效。</p>
<h3 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a><strong>轻量级锁</strong></h3><p>如果锁偏向失败,JVM 不会立即挂起线程，还会使用一种称为轻量级锁的优化手段.会将对象的头部作为指针，指向持有锁的线程堆栈内部， 来判断一个线程是否持有对象锁。 如果线程获得轻量级锁成功， 就进入临界区。 如果获得轻量级锁失败，表示其他线程抢到了锁，那么当前线程的锁的请求就膨胀为重量级锁。当前线程就转到阻塞队列中变为阻塞状态。</p>
<p>偏向锁，轻量级锁都是乐观锁，重量级锁是悲观锁。</p>
<p>一个对象刚开始实例化时,没有任何线程访问它,它是可偏向的,即它认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程. 偏向第一个线程,这个线程在修改对象头成为偏向锁时使用 CAS 操作,将对象头中 ThreadId 改成自己的 ID,之后再访问这个对象时,只需要对比 ID 即可. 一旦有第二个线程访问该对象,因为偏向锁不会主动释放,所以第二个线程可以查看对象的偏向状态,当第二个线程访问对象时,表示在这个对象上已经存在竞争了,检查原来持有对象锁的线程是否存活,如果挂了则将对象变为无锁状态,然后重新偏向新的线程; 如果原来的线程依然存活,则马上执行原来线程的栈,检查该对象的使用情况,如果仍然需要偏向锁,则偏向锁升级为轻量级锁.</p>
<p>轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对同一个锁的操作会错开,或者稍微等待一下(自旋)另外一个线程就会释放锁. 当自旋超过一定次数,或者一个线程持有锁,一个线程在自旋,又来第三个线程访问时, 轻量级锁会膨胀为重量级锁, 重量级锁除了持有锁的线程外,其他的线程都阻塞.</p>
<hr>
<p>CAS、乐观锁、悲观锁</p>
<p>悲观锁：当一个线程去获取锁之后，另一个线程来获取锁会阻塞等待，常见的synchronized就是悲观锁</p>
<p>乐观锁：当一个线程去操作的时候，其他的线程也可以操作，操作提交是根据版本控制的，CAS就是乐观锁，通过比较预期值与实际值的比较考虑是否提交更新。悲观锁适合写场景多的情况，乐观锁适合读场景多的情况</p>
<p>公平锁：所有线程都能公平的获取到锁，线程申请锁之后会排队到一个线程组里，根据申请顺序去获取锁的使用</p>
<p>非公平锁：不是所有线程都能拿到锁，当一个线程去请求的时候可能直接插队拿到了锁，有一些线程可能会饿死，常用的synchronized、ReentrantLock</p>
<p>可重入锁：在使用锁之后还可以申请获取锁</p>
<p>不可重入锁：在获取锁之后不可在内部重新获取锁</p>
<p>小结：可重入锁能一定程度的避免死锁 synchronized、ReentrantLock 重入锁</p>
<p>自旋锁：在获取锁的时候如果有其他线程占用锁，那么该线程就会一直循环去获取锁，一直到获取到为止</p>
<p>共享锁：一个线程占用锁之后，其他线程可以读数据，但不能修改\新增\删除数据</p>
<p>互斥锁：一个线程占用锁之后，其他线程不可以进行任何操作</p>
<p>死锁：两个或两个以上的线程在执行过程中，由于竞争资源或者由于彼此通信而造成的一种阻塞的现象，若无外力作用，它们都将无法让程序进行下去</p>
<hr>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/wang-cheng/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/wang-cheng/page/4/">4</a><a class="extend next" rel="next" href="/wang-cheng/page/2/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">王诚</p>
  <div class="site-description" itemprop="description">命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/wang-cheng/archives/">
        
          <span class="site-state-item-count">32</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/wang-cheng/tags/">
          
        <span class="site-state-item-count">12</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">王诚</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/wang-cheng/lib/anime.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.ui.min.js"></script>

<script src="/wang-cheng/js/utils.js"></script>

<script src="/wang-cheng/js/motion.js"></script>


<script src="/wang-cheng/js/schemes/pisces.js"></script>


<script src="/wang-cheng/js/next-boot.js"></script>




  















  

  

  

</body>
</html>
